Translations update
[openttd/fttd.git] / src / gfx.cpp
blob60d483ddd290488487eba3892ca26a61f0772215
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 "debug.h"
14 #include "gfx_layout.h"
15 #include "progress.h"
16 #include "zoom_func.h"
17 #include "blitter/blitter.h"
18 #include "video/video_driver.hpp"
19 #include "strings_func.h"
20 #include "settings_type.h"
21 #include "network/network.h"
22 #include "network/network_func.h"
23 #include "window_func.h"
24 #include "newgrf_debug.h"
26 #include "table/palettes.h"
27 #include "table/string_colours.h"
28 #include "table/sprites.h"
29 #include "table/control_codes.h"
31 byte _dirkeys; ///< 1 = left, 2 = up, 4 = right, 8 = down
32 bool _fullscreen;
33 byte _support8bpp;
34 CursorVars _cursor;
35 bool _ctrl_pressed; ///< Is Ctrl pressed?
36 bool _shift_pressed; ///< Is Shift pressed?
37 byte _fast_forward;
38 bool _left_button_down; ///< Is left mouse button pressed?
39 bool _left_button_clicked; ///< Is left mouse button clicked?
40 bool _right_button_down; ///< Is right mouse button pressed?
41 bool _right_button_clicked; ///< Is right mouse button clicked?
42 DrawPixelInfo _screen;
43 bool _screen_disable_anim = false; ///< Disable palette animation (important for 32bpp-anim blitter during giant screenshot)
44 bool _exit_game;
45 GameMode _game_mode;
46 SwitchMode _switch_mode; ///< The next mainloop command.
47 PauseModeByte _pause_mode;
48 Palette _cur_palette;
50 /** Cache with metrics of some glyphs of a font. */
51 struct FontMetrics {
52 byte widths [256 - 32]; ///< Glyph widths of all ASCII characters.
53 byte widest_digit; ///< Widest digit.
54 byte widest_digit_nonnull; ///< Widest leading (non-null) digit.
55 byte digit_width; ///< Width of the widest digit.
58 static FontMetrics font_metrics_cache [FS_END]; ///< Cache containing width of often used characters. @see GetCharacterWidth()
60 DrawPixelInfo *_cur_dpi;
61 byte _colour_gradient[COLOUR_END][8];
63 static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL, SpriteID sprite_id = SPR_CURSOR_MOUSE);
64 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);
66 static ReusableBuffer<uint8> _cursor_backup;
68 ZoomLevelByte _gui_zoom; ///< GUI Zoom level
70 /**
71 * The rect for repaint.
73 * This rectangle defines the area which should be repaint by the video driver.
75 * @ingroup dirty
77 static Rect _invalid_rect;
78 static const byte *_colour_remap_ptr;
79 static byte _string_colourremap[3]; ///< Recoloursprite for stringdrawing. The grf loader ensures that #ST_FONT sprites only use colours 0 to 2.
81 static const uint DIRTY_BLOCK_HEIGHT = 8;
82 static const uint DIRTY_BLOCK_WIDTH = 64;
84 static uint _dirty_bytes_per_line = 0;
85 static byte *_dirty_blocks = NULL;
86 extern uint _dirty_block_colour;
88 void GfxScroll(int left, int top, int width, int height, int xo, int yo)
90 Blitter *blitter = Blitter::get();
92 if (xo == 0 && yo == 0) return;
94 if (_cursor.visible) UndrawMouseCursor();
96 #ifdef ENABLE_NETWORK
97 if (_networking) NetworkUndrawChatMessage();
98 #endif /* ENABLE_NETWORK */
100 blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo);
101 /* This part of the screen is now dirty. */
102 VideoDriver::GetActiveDriver()->MakeDirty(left, top, width, height);
107 * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
109 * @pre dpi->zoom == ZOOM_LVL_NORMAL, right >= left, bottom >= top
110 * @param left Minimum X (inclusive)
111 * @param top Minimum Y (inclusive)
112 * @param right Maximum X (inclusive)
113 * @param bottom Maximum Y (inclusive)
114 * @param colour A 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR)
115 * @param mode
116 * FILLRECT_OPAQUE: Fill the rectangle with the specified colour
117 * FILLRECT_CHECKER: Like FILLRECT_OPAQUE, but only draw every second pixel (used to grey out things)
118 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the rectangle currently on screen
120 void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
122 Blitter *blitter = Blitter::get();
123 const DrawPixelInfo *dpi = _cur_dpi;
124 void *dst;
125 const int otop = top;
126 const int oleft = left;
128 if (dpi->zoom != ZOOM_LVL_NORMAL) return;
129 if (left > right || top > bottom) return;
130 if (right < dpi->left || left >= dpi->left + dpi->width) return;
131 if (bottom < dpi->top || top >= dpi->top + dpi->height) return;
133 if ( (left -= dpi->left) < 0) left = 0;
134 right = right - dpi->left + 1;
135 if (right > dpi->width) right = dpi->width;
136 right -= left;
137 assert(right > 0);
139 if ( (top -= dpi->top) < 0) top = 0;
140 bottom = bottom - dpi->top + 1;
141 if (bottom > dpi->height) bottom = dpi->height;
142 bottom -= top;
143 assert(bottom > 0);
145 dst = blitter->MoveTo(dpi->dst_ptr, left, top);
147 switch (mode) {
148 default: // FILLRECT_OPAQUE
149 blitter->DrawRect(dst, right, bottom, (uint8)colour);
150 break;
152 case FILLRECT_RECOLOUR:
153 blitter->DrawColourMappingRect(dst, right, bottom, GB(colour, 0, PALETTE_WIDTH));
154 break;
156 case FILLRECT_CHECKER: {
157 byte bo = (oleft - left + dpi->left + otop - top + dpi->top) & 1;
158 do {
159 for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, (uint8)colour);
160 dst = blitter->MoveTo(dst, 0, 1);
161 } while (--bottom > 0);
162 break;
168 * Check line clipping by using a linear equation and draw the visible part of
169 * the line given by x/y and x2/y2.
170 * @param video Destination pointer to draw into.
171 * @param x X coordinate of first point.
172 * @param y Y coordinate of first point.
173 * @param x2 X coordinate of second point.
174 * @param y2 Y coordinate of second point.
175 * @param screen_width With of the screen to check clipping against.
176 * @param screen_height Height of the screen to check clipping against.
177 * @param colour Colour of the line.
178 * @param width Width of the line.
179 * @param dash Length of dashes for dashed lines. 0 means solid line.
181 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)
183 Blitter *blitter = Blitter::get();
185 assert(width > 0);
187 if (y2 == y || x2 == x) {
188 /* Special case: horizontal/vertical line. All checks already done in GfxPreprocessLine. */
189 blitter->DrawLine(video, x, y, x2, y2, screen_width, screen_height, colour, width, dash);
190 return;
193 int grade_y = y2 - y;
194 int grade_x = x2 - x;
196 /* Clipping rectangle. Slightly extended so we can ignore the width of the line. */
197 uint extra = CeilDiv(3 * width, 4); // not less then "width * sqrt(2) / 2"
198 Rect clip = { -extra, -extra, screen_width - 1 + extra, screen_height - 1 + extra };
200 /* prevent integer overflows. */
201 int margin = 1;
202 while (INT_MAX / abs(grade_y) < max(abs(clip.left - x), abs(clip.right - x))) {
203 grade_y /= 2;
204 grade_x /= 2;
205 margin *= 2; // account for rounding errors
208 /* Imagine that the line is infinitely long and it intersects with
209 * infinitely long left and right edges of the clipping rectangle.
210 * If both intersection points are outside the clipping rectangle
211 * and both on the same side of it, we don't need to draw anything. */
212 int left_isec_y = y + (clip.left - x) * grade_y / grade_x;
213 int right_isec_y = y + (clip.right - x) * grade_y / grade_x;
214 if ((left_isec_y > clip.bottom + margin && right_isec_y > clip.bottom + margin) ||
215 (left_isec_y < clip.top - margin && right_isec_y < clip.top - margin)) {
216 return;
219 /* It is possible to use the line equation to further reduce the amount of
220 * work the blitter has to do by shortening the effective line segment.
221 * However, in order to get that right and prevent the flickering effects
222 * of rounding errors so much additional code has to be run here that in
223 * the general case the effect is not noticable. */
225 blitter->DrawLine(video, x, y, x2, y2, screen_width, screen_height, colour, width, dash);
229 * Align parameters of a line to the given DPI and check simple clipping.
230 * @param dpi Screen parameters to align with.
231 * @param x X coordinate of first point.
232 * @param y Y coordinate of first point.
233 * @param x2 X coordinate of second point.
234 * @param y2 Y coordinate of second point.
235 * @param width Width of the line.
236 * @return True if the line is likely to be visible, false if it's certainly
237 * invisible.
239 static inline bool GfxPreprocessLine(DrawPixelInfo *dpi, int &x, int &y, int &x2, int &y2, int width)
241 x -= dpi->left;
242 x2 -= dpi->left;
243 y -= dpi->top;
244 y2 -= dpi->top;
246 /* Check simple clipping */
247 if (x + width / 2 < 0 && x2 + width / 2 < 0 ) return false;
248 if (y + width / 2 < 0 && y2 + width / 2 < 0 ) return false;
249 if (x - width / 2 > dpi->width && x2 - width / 2 > dpi->width ) return false;
250 if (y - width / 2 > dpi->height && y2 - width / 2 > dpi->height) return false;
251 return true;
254 void GfxDrawLine(int x, int y, int x2, int y2, int colour, int width, int dash)
256 DrawPixelInfo *dpi = _cur_dpi;
257 if (GfxPreprocessLine(dpi, x, y, x2, y2, width)) {
258 GfxDoDrawLine(dpi->dst_ptr, x, y, x2, y2, dpi->width, dpi->height, colour, width, dash);
262 void GfxDrawLineUnscaled(int x, int y, int x2, int y2, int colour)
264 DrawPixelInfo *dpi = _cur_dpi;
265 if (GfxPreprocessLine(dpi, x, y, x2, y2, 1)) {
266 GfxDoDrawLine(dpi->dst_ptr,
267 UnScaleByZoom(x, dpi->zoom), UnScaleByZoom(y, dpi->zoom),
268 UnScaleByZoom(x2, dpi->zoom), UnScaleByZoom(y2, dpi->zoom),
269 UnScaleByZoom(dpi->width, dpi->zoom), UnScaleByZoom(dpi->height, dpi->zoom), colour, 1);
274 * Draws the projection of a parallelepiped.
275 * This can be used to draw boxes in world coordinates.
277 * @param x Screen X-coordinate of top front corner.
278 * @param y Screen Y-coordinate of top front corner.
279 * @param dx1 Screen X-length of first edge.
280 * @param dy1 Screen Y-length of first edge.
281 * @param dx2 Screen X-length of second edge.
282 * @param dy2 Screen Y-length of second edge.
283 * @param dx3 Screen X-length of third edge.
284 * @param dy3 Screen Y-length of third edge.
286 void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3)
288 /* ....
289 * .. ....
290 * .. ....
291 * .. ^
292 * <--__(dx1,dy1) /(dx2,dy2)
293 * : --__ / :
294 * : --__ / :
295 * : *(x,y) :
296 * : | :
297 * : | ..
298 * .... |(dx3,dy3)
299 * .... | ..
300 * ....V.
303 static const byte colour = PC_WHITE;
305 GfxDrawLineUnscaled(x, y, x + dx1, y + dy1, colour);
306 GfxDrawLineUnscaled(x, y, x + dx2, y + dy2, colour);
307 GfxDrawLineUnscaled(x, y, x + dx3, y + dy3, colour);
309 GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx2, y + dy1 + dy2, colour);
310 GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx3, y + dy1 + dy3, colour);
311 GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx1, y + dy2 + dy1, colour);
312 GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx3, y + dy2 + dy3, colour);
313 GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx1, y + dy3 + dy1, colour);
314 GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx2, y + dy3 + dy2, colour);
318 * Set the colour remap to be for the given colour.
319 * @param colour the new colour of the remap.
321 static void SetColourRemap(TextColour colour)
323 if (colour == TC_INVALID) return;
325 /* Black strings have no shading ever; the shading is black, so it
326 * would be invisible at best, but it actually makes it illegible. */
327 bool no_shade = (colour & TC_NO_SHADE) != 0 || colour == TC_BLACK;
328 bool raw_colour = (colour & TC_IS_PALETTE_COLOUR) != 0;
329 colour &= ~(TC_NO_SHADE | TC_IS_PALETTE_COLOUR);
331 _string_colourremap[1] = raw_colour ? (byte)colour : _string_colourmap[colour];
332 _string_colourremap[2] = no_shade ? 0 : 1;
333 _colour_remap_ptr = _string_colourremap;
337 * Drawing routine for drawing a laid out line of text.
338 * @param line String to draw.
339 * @param y The top most position to draw on.
340 * @param left The left most position to draw on.
341 * @param right The right most position to draw on.
342 * @param align The alignment of the string when drawing left-to-right. In the
343 * case a right-to-left language is chosen this is inverted so it
344 * will be drawn in the right direction.
345 * @param underline Whether to underline what has been drawn or not.
346 * @param truncation Whether to perform string truncation or not.
348 * @return In case of left or center alignment the right most pixel we have drawn to.
349 * In case of right alignment the left most pixel we have drawn to.
351 static int DrawLayoutLine(const ParagraphLayouter::Line *line, int y, int left, int right, StringAlignment align, bool underline, bool truncation)
353 if (line->CountRuns() == 0) return 0;
355 int w = line->GetWidth();
356 int h = line->GetLeading();
359 * The following is needed for truncation.
360 * Depending on the text direction, we either remove bits at the rear
361 * or the front. For this we shift the entire area to draw so it fits
362 * within the left/right bounds and the side we do not truncate it on.
363 * Then we determine the truncation location, i.e. glyphs that fall
364 * outside of the range min_x - max_x will not be drawn; they are thus
365 * the truncated glyphs.
367 * At a later step we insert the dots.
370 int max_w = right - left + 1; // The maximum width.
372 int offset_x = 0; // The offset we need for positioning the glyphs
373 int min_x = left; // The minimum x position to draw normal glyphs on.
374 int max_x = right; // The maximum x position to draw normal glyphs on.
376 truncation &= max_w < w; // Whether we need to do truncation.
377 int dot_width = 0; // Cache for the width of the dot.
378 const Sprite *dot_sprite = NULL; // Cache for the sprite of the dot.
380 if (truncation) {
382 * Assumption may be made that all fonts of a run are of the same size.
383 * In any case, we'll use these dots for the abbreviation, so even if
384 * another size would be chosen it won't have truncated too little for
385 * the truncation dots.
387 FontCache *fc = ((const Font*)line->GetVisualRun(0)->GetFont())->fc;
388 GlyphID dot_glyph = fc->MapCharToGlyph('.');
389 dot_width = fc->GetGlyphWidth(dot_glyph);
390 dot_sprite = fc->GetGlyph(dot_glyph);
392 if (_current_text_dir == TD_RTL) {
393 min_x += 3 * dot_width;
394 offset_x = w - 3 * dot_width - max_w;
395 } else {
396 max_x -= 3 * dot_width;
399 w = max_w;
402 /* In case we have a RTL language we swap the alignment. */
403 if (!(align & SA_FORCE) && _current_text_dir == TD_RTL && (align & SA_HOR_MASK) != SA_HOR_CENTER) align ^= SA_RIGHT;
405 /* right is the right most position to draw on. In this case we want to do
406 * calculations with the width of the string. In comparison right can be
407 * seen as lastof(todraw) and width as lengthof(todraw). They differ by 1.
408 * So most +1/-1 additions are to move from lengthof to 'indices'.
410 switch (align & SA_HOR_MASK) {
411 case SA_LEFT:
412 /* right + 1 = left + w */
413 right = left + w - 1;
414 break;
416 case SA_HOR_CENTER:
417 left = RoundDivSU(right + 1 + left - w, 2);
418 /* right + 1 = left + w */
419 right = left + w - 1;
420 break;
422 case SA_RIGHT:
423 left = right + 1 - w;
424 break;
426 default:
427 NOT_REACHED();
430 TextColour colour = TC_BLACK;
431 bool draw_shadow = false;
432 for (int run_index = 0; run_index < line->CountRuns(); run_index++) {
433 const ParagraphLayouter::VisualRun *run = line->GetVisualRun(run_index);
434 const Font *f = (const Font*)run->GetFont();
436 FontCache *fc = f->fc;
437 colour = f->colour;
438 SetColourRemap(colour);
440 DrawPixelInfo *dpi = _cur_dpi;
441 int dpi_left = dpi->left;
442 int dpi_right = dpi->left + dpi->width - 1;
444 draw_shadow = colour != TC_BLACK && (colour & TC_NO_SHADE) == 0 && fc->GetDrawGlyphShadow();
446 for (int i = 0; i < run->GetGlyphCount(); i++) {
447 GlyphID glyph = run->GetGlyphs()[i];
449 /* Not a valid glyph (empty) */
450 if (glyph == 0xFFFF) continue;
452 int begin_x = (int)run->GetPositions()[i * 2] + left - offset_x;
453 int end_x = (int)run->GetPositions()[i * 2 + 2] + left - offset_x - 1;
454 int top = (int)run->GetPositions()[i * 2 + 1] + y;
456 /* Truncated away. */
457 if (truncation && (begin_x < min_x || end_x > max_x)) continue;
459 const Sprite *sprite = fc->GetGlyph(glyph);
460 /* Check clipping (the "+ 1" is for the shadow). */
461 if (begin_x + sprite->x_offs > dpi_right || begin_x + sprite->x_offs + sprite->width /* - 1 + 1 */ < dpi_left) continue;
463 if (draw_shadow && (glyph & SPRITE_GLYPH) == 0) {
464 SetColourRemap(TC_BLACK);
465 GfxMainBlitter(sprite, begin_x + 1, top + 1, BM_COLOUR_REMAP);
466 SetColourRemap(colour);
468 GfxMainBlitter(sprite, begin_x, top, BM_COLOUR_REMAP);
472 if (truncation) {
473 int x = (_current_text_dir == TD_RTL) ? left : (right - 3 * dot_width);
474 for (int i = 0; i < 3; i++, x += dot_width) {
475 if (draw_shadow) {
476 SetColourRemap(TC_BLACK);
477 GfxMainBlitter(dot_sprite, x + 1, y + 1, BM_COLOUR_REMAP);
478 SetColourRemap(colour);
480 GfxMainBlitter(dot_sprite, x, y, BM_COLOUR_REMAP);
484 if (underline) {
485 GfxFillRect(left, y + h, right, y + h, _string_colourremap[1]);
488 return (align & SA_HOR_MASK) == SA_RIGHT ? left : right;
492 * Draw string, possibly truncated to make it fit in its allocated space
494 * @param left The left most position to draw on.
495 * @param right The right most position to draw on.
496 * @param top The top most position to draw on.
497 * @param str String to draw.
498 * @param colour Colour used for drawing the string, see DoDrawString() for details
499 * @param align The alignment of the string when drawing left-to-right. In the
500 * case a right-to-left language is chosen this is inverted so it
501 * will be drawn in the right direction.
502 * @param underline Whether to underline what has been drawn or not.
503 * @param fontsize The size of the initial characters.
504 * @return In case of left or center alignment the right most pixel we have drawn to.
505 * In case of right alignment the left most pixel we have drawn to.
507 int DrawString(int left, int right, int top, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
509 /* The string may contain control chars to change the font, just use the biggest font for clipping. */
510 int max_height = max(max(FONT_HEIGHT_SMALL, FONT_HEIGHT_NORMAL), max(FONT_HEIGHT_LARGE, FONT_HEIGHT_MONO));
512 /* Funny glyphs may extent outside the usual bounds, so relax the clipping somewhat. */
513 int extra = max_height / 2;
515 if (_cur_dpi->top + _cur_dpi->height + extra < top || _cur_dpi->top > top + max_height + extra ||
516 _cur_dpi->left + _cur_dpi->width + extra < left || _cur_dpi->left > right + extra) {
517 return 0;
520 Layouter layout(str, INT32_MAX, colour, fontsize);
521 if (layout.Length() == 0) return 0;
523 return DrawLayoutLine(*layout.Begin(), top, left, right, align, underline, true);
527 * Draw string, possibly truncated to make it fit in its allocated space
529 * @param left The left most position to draw on.
530 * @param right The right most position to draw on.
531 * @param top The top most position to draw on.
532 * @param str String to draw.
533 * @param colour Colour used for drawing the string, see DoDrawString() for details
534 * @param align The alignment of the string when drawing left-to-right. In the
535 * case a right-to-left language is chosen this is inverted so it
536 * will be drawn in the right direction.
537 * @param underline Whether to underline what has been drawn or not.
538 * @param fontsize The size of the initial characters.
539 * @return In case of left or center alignment the right most pixel we have drawn to.
540 * In case of right alignment the left most pixel we have drawn to.
542 int DrawString(int left, int right, int top, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
544 char buffer[DRAW_STRING_BUFFER];
545 GetString (buffer, str);
546 return DrawString(left, right, top, buffer, colour, align, underline, fontsize);
550 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
551 * @param str string to check
552 * @param maxw maximum string width
553 * @return height of pixels of string when it is drawn
555 uint GetStringHeight (const char *str, int maxw, FontSize fontsize)
557 Layouter layout(str, maxw, TC_FROMSTRING, fontsize);
558 return layout.GetBounds().height;
562 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
563 * @param str string to check
564 * @param maxw maximum string width
565 * @return height of pixels of string when it is drawn
567 uint GetStringHeight (StringID str, int maxw)
569 char buffer[DRAW_STRING_BUFFER];
570 GetString (buffer, str);
571 return GetStringHeight(buffer, maxw);
575 * Calculates number of lines of string. The string is changed to a multiline string if needed.
576 * @param str string to check
577 * @param maxw maximum string width
578 * @return number of lines of string when it is drawn
580 int GetStringLineCount(StringID str, int maxw)
582 char buffer[DRAW_STRING_BUFFER];
583 GetString (buffer, str);
585 Layouter layout(buffer, maxw);
586 return layout.Length();
590 * Draw string, possibly over multiple lines.
592 * @param left The left most position to draw on.
593 * @param right The right most position to draw on.
594 * @param top The top most position to draw on.
595 * @param bottom The bottom most position to draw on.
596 * @param str String to draw.
597 * @param colour Colour used for drawing the string, see DoDrawString() for details
598 * @param align The horizontal and vertical alignment of the string.
599 * @param underline Whether to underline all strings
600 * @param fontsize The size of the initial characters.
602 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
604 int DrawStringMultiLine(int left, int right, int top, int bottom, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
606 int maxw = right - left + 1;
607 int maxh = bottom - top + 1;
609 /* It makes no sense to even try if it can't be drawn anyway, or
610 * do we really want to support fonts of 0 or less pixels high? */
611 if (maxh <= 0) return top;
613 Layouter layout(str, maxw, colour, fontsize);
614 int total_height = layout.GetBounds().height;
615 int y;
616 switch (align & SA_VERT_MASK) {
617 case SA_TOP:
618 y = top;
619 break;
621 case SA_VERT_CENTER:
622 y = RoundDivSU(bottom + top - total_height, 2);
623 break;
625 case SA_BOTTOM:
626 y = bottom - total_height;
627 break;
629 default: NOT_REACHED();
632 int last_line = top;
633 int first_line = bottom;
635 for (const ParagraphLayouter::Line **iter = layout.Begin(); iter != layout.End(); iter++) {
636 const ParagraphLayouter::Line *line = *iter;
638 int line_height = line->GetLeading();
639 if (y >= top && y < bottom) {
640 last_line = y + line_height;
641 if (first_line > y) first_line = y;
643 DrawLayoutLine(line, y, left, right, align, underline, false);
645 y += line_height;
648 return ((align & SA_VERT_MASK) == SA_BOTTOM) ? first_line : last_line;
652 * Draw string, possibly over multiple lines.
654 * @param left The left most position to draw on.
655 * @param right The right most position to draw on.
656 * @param top The top most position to draw on.
657 * @param bottom The bottom most position to draw on.
658 * @param str String to draw.
659 * @param colour Colour used for drawing the string, see DoDrawString() for details
660 * @param align The horizontal and vertical alignment of the string.
661 * @param underline Whether to underline all strings
662 * @param fontsize The size of the initial characters.
664 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
666 int DrawStringMultiLine(int left, int right, int top, int bottom, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
668 char buffer[DRAW_STRING_BUFFER];
669 GetString (buffer, str);
670 return DrawStringMultiLine(left, right, top, bottom, buffer, colour, align, underline, fontsize);
674 * Return the string dimension in pixels. The height and width are returned
675 * in a single Dimension value. TINYFONT, BIGFONT modifiers are only
676 * supported as the first character of the string. The returned dimensions
677 * are therefore a rough estimation correct for all the current strings
678 * but not every possible combination
679 * @param str string to calculate pixel-width
680 * @param start_fontsize Fontsize to start the text with
681 * @return string width and height in pixels
683 Dimension GetStringBoundingBox(const char *str, FontSize start_fontsize)
685 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
686 return layout.GetBounds();
690 * Get bounding box of a string. Uses parameters set by #DParam if needed.
691 * Has the same restrictions as #GetStringBoundingBox(const char *str).
692 * @param strid String to examine.
693 * @return Width and height of the bounding box for the string in pixels.
695 Dimension GetStringBoundingBox(StringID strid)
697 char buffer[DRAW_STRING_BUFFER];
699 GetString (buffer, strid);
700 return GetStringBoundingBox(buffer);
704 * Get the leading corner of a character in a single-line string relative
705 * to the start of the string.
706 * @param str String containing the character.
707 * @param ch Pointer to the character in the string.
708 * @param start_fontsize Font size to start the text with.
709 * @return Upper left corner of the glyph associated with the character.
711 Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsize)
713 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
714 return layout.GetCharPosition(ch);
718 * Get the character from a string that is drawn at a specific position.
719 * @param str String to test.
720 * @param x Position relative to the start of the string.
721 * @param start_fontsize Font size to start the text with.
722 * @return Pointer to the character at the position or NULL if there is no character at the position.
724 const char *GetCharAtPosition(const char *str, int x, FontSize start_fontsize)
726 if (x < 0) return NULL;
728 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
729 return layout.GetCharAtPosition(x);
733 * Draw single character horizontally centered around (x,y)
734 * @param c Character (glyph) to draw
735 * @param x X position to draw character
736 * @param y Y position to draw character
737 * @param colour Colour to use, see DoDrawString() for details
739 void DrawCharCentered(WChar c, int x, int y, TextColour colour)
741 SetColourRemap(colour);
742 GfxMainBlitter(GetGlyph(FS_NORMAL, c), x - GetCharacterWidth(FS_NORMAL, c) / 2, y, BM_COLOUR_REMAP);
746 * Get the size of a sprite.
747 * @param sprid Sprite to examine.
748 * @param [out] offset Optionally returns the sprite position offset.
749 * @return Sprite size in pixels.
750 * @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.
752 Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
754 const Sprite *sprite = GetSprite(sprid, ST_NORMAL);
756 if (offset != NULL) {
757 offset->x = UnScaleByZoom(sprite->x_offs, zoom);
758 offset->y = UnScaleByZoom(sprite->y_offs, zoom);
761 Dimension d;
762 d.width = max<int>(0, UnScaleByZoom(sprite->x_offs + sprite->width, zoom));
763 d.height = max<int>(0, UnScaleByZoom(sprite->y_offs + sprite->height, zoom));
764 return d;
768 * Helper function to get the blitter mode for different types of palettes.
769 * @param pal The palette to get the blitter mode for.
770 * @return The blitter mode associated with the palette.
772 static BlitterMode GetBlitterMode(PaletteID pal)
774 switch (pal) {
775 case PAL_NONE: return BM_NORMAL;
776 case PALETTE_CRASH: return BM_CRASH_REMAP;
777 case PALETTE_ALL_BLACK: return BM_BLACK_REMAP;
778 default: return BM_COLOUR_REMAP;
783 * Draw a sprite in a viewport.
784 * @param img Image number to draw
785 * @param pal Palette to use.
786 * @param x Left coordinate of image in viewport, scaled by zoom
787 * @param y Top coordinate of image in viewport, scaled by zoom
788 * @param sub If available, draw only specified part of the sprite
790 void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub)
792 SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
793 if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
794 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
795 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite);
796 } else if (pal != PAL_NONE) {
797 if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) {
798 SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH));
799 } else {
800 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
802 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, GetBlitterMode(pal), sub, real_sprite);
803 } else {
804 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite);
809 * Draw a sprite, not in a viewport
810 * @param img Image number to draw
811 * @param pal Palette to use.
812 * @param x Left coordinate of image in pixels
813 * @param y Top coordinate of image in pixels
814 * @param sub If available, draw only specified part of the sprite
815 * @param zoom Zoom level of sprite
817 void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub, ZoomLevel zoom)
819 SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
820 if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
821 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
822 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite, zoom);
823 } else if (pal != PAL_NONE) {
824 if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) {
825 SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH));
826 } else {
827 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
829 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, GetBlitterMode(pal), sub, real_sprite, zoom);
830 } else {
831 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite, zoom);
836 * The code for setting up the blitter mode and sprite information before finally drawing the sprite.
837 * @param sprite The sprite to draw.
838 * @param x The X location to draw.
839 * @param y The Y location to draw.
840 * @param mode The settings for the blitter to pass.
841 * @param sub Whether to only draw a sub set of the sprite.
842 * @param zoom The zoom level at which to draw the sprites.
843 * @tparam ZOOM_BASE The factor required to get the sub sprite information into the right size.
844 * @tparam SCALED_XY Whether the X and Y are scaled or unscaled.
846 template <int ZOOM_BASE, bool SCALED_XY>
847 static void GfxBlitter(const Sprite * const sprite, int x, int y, BlitterMode mode, const SubSprite * const sub, SpriteID sprite_id, ZoomLevel zoom)
849 const DrawPixelInfo *dpi = _cur_dpi;
850 Blitter::BlitterParams bp;
852 if (SCALED_XY) {
853 /* Scale it */
854 x = ScaleByZoom(x, zoom);
855 y = ScaleByZoom(y, zoom);
858 /* Move to the correct offset */
859 x += sprite->x_offs;
860 y += sprite->y_offs;
862 if (sub == NULL) {
863 /* No clipping. */
864 bp.skip_left = 0;
865 bp.skip_top = 0;
866 bp.width = UnScaleByZoom(sprite->width, zoom);
867 bp.height = UnScaleByZoom(sprite->height, zoom);
868 } else {
869 /* Amount of pixels to clip from the source sprite */
870 int clip_left = max(0, -sprite->x_offs + sub->left * ZOOM_BASE );
871 int clip_top = max(0, -sprite->y_offs + sub->top * ZOOM_BASE );
872 int clip_right = max(0, sprite->width - (-sprite->x_offs + (sub->right + 1) * ZOOM_BASE));
873 int clip_bottom = max(0, sprite->height - (-sprite->y_offs + (sub->bottom + 1) * ZOOM_BASE));
875 if (clip_left + clip_right >= sprite->width) return;
876 if (clip_top + clip_bottom >= sprite->height) return;
878 bp.skip_left = UnScaleByZoomLower(clip_left, zoom);
879 bp.skip_top = UnScaleByZoomLower(clip_top, zoom);
880 bp.width = UnScaleByZoom(sprite->width - clip_left - clip_right, zoom);
881 bp.height = UnScaleByZoom(sprite->height - clip_top - clip_bottom, zoom);
883 x += ScaleByZoom(bp.skip_left, zoom);
884 y += ScaleByZoom(bp.skip_top, zoom);
887 /* Copy the main data directly from the sprite */
888 bp.sprite = sprite;
889 bp.top = 0;
890 bp.left = 0;
892 bp.dst = dpi->dst_ptr;
893 bp.pitch = dpi->pitch;
894 bp.remap = _colour_remap_ptr;
896 assert(sprite->width > 0);
897 assert(sprite->height > 0);
899 if (bp.width <= 0) return;
900 if (bp.height <= 0) return;
902 y -= SCALED_XY ? ScaleByZoom(dpi->top, zoom) : dpi->top;
903 int y_unscaled = UnScaleByZoom(y, zoom);
904 /* Check for top overflow */
905 if (y < 0) {
906 bp.height -= -y_unscaled;
907 if (bp.height <= 0) return;
908 bp.skip_top += -y_unscaled;
909 y = 0;
910 } else {
911 bp.top = y_unscaled;
914 /* Check for bottom overflow */
915 y += SCALED_XY ? ScaleByZoom(bp.height - dpi->height, zoom) : ScaleByZoom(bp.height, zoom) - dpi->height;
916 if (y > 0) {
917 bp.height -= UnScaleByZoom(y, zoom);
918 if (bp.height <= 0) return;
921 x -= SCALED_XY ? ScaleByZoom(dpi->left, zoom) : dpi->left;
922 int x_unscaled = UnScaleByZoom(x, zoom);
923 /* Check for left overflow */
924 if (x < 0) {
925 bp.width -= -x_unscaled;
926 if (bp.width <= 0) return;
927 bp.skip_left += -x_unscaled;
928 x = 0;
929 } else {
930 bp.left = x_unscaled;
933 /* Check for right overflow */
934 x += SCALED_XY ? ScaleByZoom(bp.width - dpi->width, zoom) : ScaleByZoom(bp.width, zoom) - dpi->width;
935 if (x > 0) {
936 bp.width -= UnScaleByZoom(x, zoom);
937 if (bp.width <= 0) return;
940 assert(bp.skip_left + bp.width <= UnScaleByZoom(sprite->width, zoom));
941 assert(bp.skip_top + bp.height <= UnScaleByZoom(sprite->height, zoom));
943 /* We do not want to catch the mouse. However we also use that spritenumber for unknown (text) sprites. */
944 if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && sprite_id != SPR_CURSOR_MOUSE) {
945 Blitter *blitter = Blitter::get();
946 void *topleft = blitter->MoveTo(bp.dst, bp.left, bp.top);
947 void *bottomright = blitter->MoveTo(topleft, bp.width - 1, bp.height - 1);
949 void *clicked = _newgrf_debug_sprite_picker.clicked_pixel;
951 if (topleft <= clicked && clicked <= bottomright) {
952 uint offset = (((size_t)clicked - (size_t)topleft) / (blitter->GetScreenDepth() / 8)) % bp.pitch;
953 if (offset < (uint)bp.width) {
954 _newgrf_debug_sprite_picker.sprites.Include(sprite_id);
959 Blitter::get()->Draw(&bp, mode, zoom);
962 static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id)
964 GfxBlitter<ZOOM_LVL_BASE, false>(sprite, x, y, mode, sub, sprite_id, _cur_dpi->zoom);
967 static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id, ZoomLevel zoom)
969 GfxBlitter<1, true>(sprite, x, y, mode, sub, sprite_id, zoom);
972 void DoPaletteAnimations();
974 void GfxInitPalettes()
976 memcpy(&_cur_palette, &_palette, sizeof(_cur_palette));
977 DoPaletteAnimations();
980 #define EXTR(p, q) (((uint16)(palette_animation_counter * (p)) * (q)) >> 16)
981 #define EXTR2(p, q) (((uint16)(~palette_animation_counter * (p)) * (q)) >> 16)
983 void DoPaletteAnimations()
985 /* Animation counter for the palette animation. */
986 static int palette_animation_counter = 0;
987 palette_animation_counter += 8;
989 Blitter *blitter = Blitter::get();
990 const Colour *s;
991 const ExtraPaletteValues *ev = &_extra_palette_values;
992 Colour old_val[PALETTE_ANIM_SIZE];
993 const uint old_tc = palette_animation_counter;
994 uint i;
995 uint j;
997 if (blitter != NULL && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
998 palette_animation_counter = 0;
1001 Colour *palette_pos = &_cur_palette.palette[PALETTE_ANIM_START]; // Points to where animations are taking place on the palette
1002 /* Makes a copy of the current animation palette in old_val,
1003 * so the work on the current palette could be compared, see if there has been any changes */
1004 memcpy(old_val, palette_pos, sizeof(old_val));
1006 /* Fizzy Drink bubbles animation */
1007 s = ev->fizzy_drink;
1008 j = EXTR2(512, EPV_CYCLES_FIZZY_DRINK);
1009 for (i = 0; i != EPV_CYCLES_FIZZY_DRINK; i++) {
1010 *palette_pos++ = s[j];
1011 j++;
1012 if (j == EPV_CYCLES_FIZZY_DRINK) j = 0;
1015 /* Oil refinery fire animation */
1016 s = ev->oil_refinery;
1017 j = EXTR2(512, EPV_CYCLES_OIL_REFINERY);
1018 for (i = 0; i != EPV_CYCLES_OIL_REFINERY; i++) {
1019 *palette_pos++ = s[j];
1020 j++;
1021 if (j == EPV_CYCLES_OIL_REFINERY) j = 0;
1024 /* Radio tower blinking */
1026 byte i = (palette_animation_counter >> 1) & 0x7F;
1027 byte v;
1029 if (i < 0x3f) {
1030 v = 255;
1031 } else if (i < 0x4A || i >= 0x75) {
1032 v = 128;
1033 } else {
1034 v = 20;
1036 palette_pos->r = v;
1037 palette_pos->g = 0;
1038 palette_pos->b = 0;
1039 palette_pos++;
1041 i ^= 0x40;
1042 if (i < 0x3f) {
1043 v = 255;
1044 } else if (i < 0x4A || i >= 0x75) {
1045 v = 128;
1046 } else {
1047 v = 20;
1049 palette_pos->r = v;
1050 palette_pos->g = 0;
1051 palette_pos->b = 0;
1052 palette_pos++;
1055 /* Handle lighthouse and stadium animation */
1056 s = ev->lighthouse;
1057 j = EXTR(256, EPV_CYCLES_LIGHTHOUSE);
1058 for (i = 0; i != EPV_CYCLES_LIGHTHOUSE; i++) {
1059 *palette_pos++ = s[j];
1060 j++;
1061 if (j == EPV_CYCLES_LIGHTHOUSE) j = 0;
1064 /* Dark blue water */
1065 s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->dark_water_toyland : ev->dark_water;
1066 j = EXTR(320, EPV_CYCLES_DARK_WATER);
1067 for (i = 0; i != EPV_CYCLES_DARK_WATER; i++) {
1068 *palette_pos++ = s[j];
1069 j++;
1070 if (j == EPV_CYCLES_DARK_WATER) j = 0;
1073 /* Glittery water */
1074 s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->glitter_water_toyland : ev->glitter_water;
1075 j = EXTR(128, EPV_CYCLES_GLITTER_WATER);
1076 for (i = 0; i != EPV_CYCLES_GLITTER_WATER / 3; i++) {
1077 *palette_pos++ = s[j];
1078 j += 3;
1079 if (j >= EPV_CYCLES_GLITTER_WATER) j -= EPV_CYCLES_GLITTER_WATER;
1082 if (blitter != NULL && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
1083 palette_animation_counter = old_tc;
1084 } else {
1085 if (memcmp(old_val, &_cur_palette.palette[PALETTE_ANIM_START], sizeof(old_val)) != 0 && _cur_palette.count_dirty == 0) {
1086 /* Did we changed anything on the palette? Seems so. Mark it as dirty */
1087 _cur_palette.first_dirty = PALETTE_ANIM_START;
1088 _cur_palette.count_dirty = PALETTE_ANIM_SIZE;
1094 * Determine a contrasty text colour for a coloured background.
1095 * @param background Background colour.
1096 * @return TC_BLACK or TC_WHITE depending on what gives a better contrast.
1098 TextColour GetContrastColour(uint8 background)
1100 Colour c = _cur_palette.palette[background];
1101 /* Compute brightness according to http://www.w3.org/TR/AERT#color-contrast.
1102 * The following formula computes 1000 * brightness^2, with brightness being in range 0 to 255. */
1103 uint sq1000_brightness = c.r * c.r * 299 + c.g * c.g * 587 + c.b * c.b * 114;
1104 /* Compare with threshold brightness 128 (50%) */
1105 return sq1000_brightness < 128 * 128 * 1000 ? TC_WHITE : TC_BLACK;
1109 * Initialize font_metrics_cache
1110 * @param monospace Whether to load the monospace cache or the normal fonts.
1112 void LoadStringWidthTable(bool monospace)
1114 for (FontSize fs = monospace ? FS_MONO : FS_BEGIN; fs < (monospace ? FS_END : FS_MONO); fs++) {
1115 for (uint i = 0; i != 224; i++) {
1116 font_metrics_cache[fs].widths[i] = GetGlyphWidth (fs, i + 32);
1119 byte widest_digit = 9;
1120 byte digit_width = font_metrics_cache[fs].widths['9' - 32];
1121 for (byte i = 8; i > 0; i--) {
1122 byte w = font_metrics_cache[fs].widths[i + '0' - 32];
1123 if (w > digit_width) {
1124 widest_digit = i;
1125 digit_width = w;
1128 font_metrics_cache[fs].widest_digit_nonnull = widest_digit;
1130 byte w = font_metrics_cache[fs].widths['0' - 32];
1131 if (w > digit_width) {
1132 widest_digit = 0;
1133 digit_width = w;
1135 font_metrics_cache[fs].widest_digit = widest_digit;
1136 font_metrics_cache[fs].digit_width = digit_width;
1139 ClearFontCache();
1140 ReInitAllWindows();
1144 * Return width of character glyph.
1145 * @param size Font of the character
1146 * @param key Character code glyph
1147 * @return Width of the character glyph
1149 byte GetCharacterWidth(FontSize size, WChar key)
1151 /* Use font_metrics_cache if possible */
1152 if (key >= 32 && key < 256) return font_metrics_cache[size].widths[key - 32];
1154 return GetGlyphWidth(size, key);
1158 * Return the maximum width of single digit.
1159 * @param size Font of the digit
1160 * @return Width of the digit.
1162 byte GetDigitWidth(FontSize size)
1164 return font_metrics_cache[size].digit_width;
1167 /** Compute the broadest n-digit value in a given font size. */
1168 uint64 GetBroadestValue (uint n, FontSize size)
1170 uint d = font_metrics_cache[size].widest_digit;
1172 if (n <= 1) return d;
1174 uint64 val = font_metrics_cache[size].widest_digit_nonnull;
1175 do {
1176 val = 10 * val + d;
1177 } while (--n > 1);
1178 return val;
1181 void ScreenSizeChanged()
1183 _dirty_bytes_per_line = CeilDiv(_screen.width, DIRTY_BLOCK_WIDTH);
1184 _dirty_blocks = xrealloct<byte>(_dirty_blocks, _dirty_bytes_per_line * CeilDiv(_screen.height, DIRTY_BLOCK_HEIGHT));
1186 /* check the dirty rect */
1187 if (_invalid_rect.right >= _screen.width) _invalid_rect.right = _screen.width;
1188 if (_invalid_rect.bottom >= _screen.height) _invalid_rect.bottom = _screen.height;
1190 /* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */
1191 _cursor.visible = false;
1194 void UndrawMouseCursor()
1196 /* Don't undraw the mouse cursor if the screen is not ready */
1197 if (_screen.dst_ptr == NULL) return;
1199 if (_cursor.visible) {
1200 Blitter *blitter = Blitter::get();
1201 _cursor.visible = false;
1202 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);
1203 VideoDriver::GetActiveDriver()->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
1207 void DrawMouseCursor()
1209 #if defined(WINCE)
1210 /* Don't ever draw the mouse for WinCE, as we work with a stylus */
1211 return;
1212 #endif
1214 /* Don't draw the mouse cursor if the screen is not ready */
1215 if (_screen.dst_ptr == NULL) return;
1217 Blitter *blitter = Blitter::get();
1218 int x;
1219 int y;
1220 int w;
1221 int h;
1223 /* Redraw mouse cursor but only when it's inside the window */
1224 if (!_cursor.in_window) return;
1226 /* Don't draw the mouse cursor if it's already drawn */
1227 if (_cursor.visible) {
1228 if (!_cursor.dirty) return;
1229 UndrawMouseCursor();
1232 w = _cursor.size.x;
1233 x = _cursor.pos.x + _cursor.offs.x + _cursor.short_vehicle_offset;
1234 if (x < 0) {
1235 w += x;
1236 x = 0;
1238 if (w > _screen.width - x) w = _screen.width - x;
1239 if (w <= 0) return;
1240 _cursor.draw_pos.x = x;
1241 _cursor.draw_size.x = w;
1243 h = _cursor.size.y;
1244 y = _cursor.pos.y + _cursor.offs.y;
1245 if (y < 0) {
1246 h += y;
1247 y = 0;
1249 if (h > _screen.height - y) h = _screen.height - y;
1250 if (h <= 0) return;
1251 _cursor.draw_pos.y = y;
1252 _cursor.draw_size.y = h;
1254 uint8 *buffer = _cursor_backup.Allocate(blitter->BufferSize(w, h));
1256 /* Make backup of stuff below cursor */
1257 blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, _cursor.draw_pos.x, _cursor.draw_pos.y), buffer, _cursor.draw_size.x, _cursor.draw_size.y);
1259 /* Draw cursor on screen */
1260 _cur_dpi = &_screen;
1261 DrawSprite(_cursor.sprite, _cursor.pal, _cursor.pos.x + _cursor.short_vehicle_offset, _cursor.pos.y);
1263 VideoDriver::GetActiveDriver()->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
1265 _cursor.visible = true;
1266 _cursor.dirty = false;
1269 void RedrawScreenRect(int left, int top, int right, int bottom)
1271 assert(right <= _screen.width && bottom <= _screen.height);
1272 if (_cursor.visible) {
1273 if (right > _cursor.draw_pos.x &&
1274 left < _cursor.draw_pos.x + _cursor.draw_size.x &&
1275 bottom > _cursor.draw_pos.y &&
1276 top < _cursor.draw_pos.y + _cursor.draw_size.y) {
1277 UndrawMouseCursor();
1281 #ifdef ENABLE_NETWORK
1282 if (_networking) NetworkUndrawChatMessage();
1283 #endif /* ENABLE_NETWORK */
1285 DrawOverlappedWindowForAll(left, top, right, bottom);
1287 VideoDriver::GetActiveDriver()->MakeDirty(left, top, right - left, bottom - top);
1291 * Repaints the rectangle blocks which are marked as 'dirty'.
1293 * @see SetDirtyBlocks
1295 void DrawDirtyBlocks()
1297 byte *b = _dirty_blocks;
1298 const int w = Align(_screen.width, DIRTY_BLOCK_WIDTH);
1299 const int h = Align(_screen.height, DIRTY_BLOCK_HEIGHT);
1300 int x;
1301 int y;
1303 if (HasModalProgress()) {
1304 /* We are generating the world, so release our rights to the map and
1305 * painting while we are waiting a bit. */
1306 _modal_progress_paint_mutex->EndCritical();
1307 _modal_progress_work_mutex->EndCritical();
1309 /* Wait a while and update _realtime_tick so we are given the rights */
1310 if (!IsFirstModalProgressLoop()) CSleep(MODAL_PROGRESS_REDRAW_TIMEOUT);
1311 _realtime_tick += MODAL_PROGRESS_REDRAW_TIMEOUT;
1312 _modal_progress_paint_mutex->BeginCritical();
1313 _modal_progress_work_mutex->BeginCritical();
1315 /* When we ended with the modal progress, do not draw the blocks.
1316 * Simply let the next run do so, otherwise we would be loading
1317 * the new state (and possibly change the blitter) when we hold
1318 * the drawing lock, which we must not do. */
1319 if (_switch_mode != SM_NONE && !HasModalProgress()) return;
1322 y = 0;
1323 do {
1324 x = 0;
1325 do {
1326 if (*b != 0) {
1327 int left;
1328 int top;
1329 int right = x + DIRTY_BLOCK_WIDTH;
1330 int bottom = y;
1331 byte *p = b;
1332 int h2;
1334 /* First try coalescing downwards */
1335 do {
1336 *p = 0;
1337 p += _dirty_bytes_per_line;
1338 bottom += DIRTY_BLOCK_HEIGHT;
1339 } while (bottom != h && *p != 0);
1341 /* Try coalescing to the right too. */
1342 h2 = (bottom - y) / DIRTY_BLOCK_HEIGHT;
1343 assert(h2 > 0);
1344 p = b;
1346 while (right != w) {
1347 byte *p2 = ++p;
1348 int h = h2;
1349 /* Check if a full line of dirty flags is set. */
1350 do {
1351 if (!*p2) goto no_more_coalesc;
1352 p2 += _dirty_bytes_per_line;
1353 } while (--h != 0);
1355 /* Wohoo, can combine it one step to the right!
1356 * Do that, and clear the bits. */
1357 right += DIRTY_BLOCK_WIDTH;
1359 h = h2;
1360 p2 = p;
1361 do {
1362 *p2 = 0;
1363 p2 += _dirty_bytes_per_line;
1364 } while (--h != 0);
1366 no_more_coalesc:
1368 left = x;
1369 top = y;
1371 if (left < _invalid_rect.left ) left = _invalid_rect.left;
1372 if (top < _invalid_rect.top ) top = _invalid_rect.top;
1373 if (right > _invalid_rect.right ) right = _invalid_rect.right;
1374 if (bottom > _invalid_rect.bottom) bottom = _invalid_rect.bottom;
1376 if (left < right && top < bottom) {
1377 RedrawScreenRect(left, top, right, bottom);
1381 } while (b++, (x += DIRTY_BLOCK_WIDTH) != w);
1382 } while (b += -(int)(w / DIRTY_BLOCK_WIDTH) + _dirty_bytes_per_line, (y += DIRTY_BLOCK_HEIGHT) != h);
1384 ++_dirty_block_colour;
1385 _invalid_rect.left = w;
1386 _invalid_rect.top = h;
1387 _invalid_rect.right = 0;
1388 _invalid_rect.bottom = 0;
1392 * This function extends the internal _invalid_rect rectangle as it
1393 * now contains the rectangle defined by the given parameters. Note
1394 * the point (0,0) is top left.
1396 * @param left The left edge of the rectangle
1397 * @param top The top edge of the rectangle
1398 * @param right The right edge of the rectangle
1399 * @param bottom The bottom edge of the rectangle
1400 * @see DrawDirtyBlocks
1402 * @todo The name of the function should be called like @c AddDirtyBlock as
1403 * it neither set a dirty rect nor add several dirty rects although
1404 * the function name is in plural. (Progman)
1406 void SetDirtyBlocks(int left, int top, int right, int bottom)
1408 byte *b;
1409 int width;
1410 int height;
1412 if (left < 0) left = 0;
1413 if (top < 0) top = 0;
1414 if (right > _screen.width) right = _screen.width;
1415 if (bottom > _screen.height) bottom = _screen.height;
1417 if (left >= right || top >= bottom) return;
1419 if (left < _invalid_rect.left ) _invalid_rect.left = left;
1420 if (top < _invalid_rect.top ) _invalid_rect.top = top;
1421 if (right > _invalid_rect.right ) _invalid_rect.right = right;
1422 if (bottom > _invalid_rect.bottom) _invalid_rect.bottom = bottom;
1424 left /= DIRTY_BLOCK_WIDTH;
1425 top /= DIRTY_BLOCK_HEIGHT;
1427 b = _dirty_blocks + top * _dirty_bytes_per_line + left;
1429 width = ((right - 1) / DIRTY_BLOCK_WIDTH) - left + 1;
1430 height = ((bottom - 1) / DIRTY_BLOCK_HEIGHT) - top + 1;
1432 assert(width > 0 && height > 0);
1434 do {
1435 int i = width;
1437 do b[--i] = 0xFF; while (i != 0);
1439 b += _dirty_bytes_per_line;
1440 } while (--height != 0);
1444 * This function mark the whole screen as dirty. This results in repainting
1445 * the whole screen. Use this with care as this function will break the
1446 * idea about marking only parts of the screen as 'dirty'.
1447 * @ingroup dirty
1449 void MarkWholeScreenDirty()
1451 SetDirtyBlocks(0, 0, _screen.width, _screen.height);
1455 * Set up a clipping area for only drawing into a certain area. To do this,
1456 * Fill a DrawPixelInfo object with the supplied relative rectangle, backup
1457 * the original (calling) _cur_dpi and assign the just returned DrawPixelInfo
1458 * _cur_dpi. When you are done, give restore _cur_dpi's original value
1459 * @param *n the DrawPixelInfo that will be the clipping rectangle box allowed
1460 * for drawing
1461 * @param left,top,width,height the relative coordinates of the clipping
1462 * rectangle relative to the current _cur_dpi. This will most likely be the
1463 * offset from the calling window coordinates
1464 * @return return false if the requested rectangle is not possible with the
1465 * current dpi pointer. Only continue of the return value is true, or you'll
1466 * get some nasty results
1468 bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
1470 Blitter *blitter = Blitter::get();
1471 const DrawPixelInfo *o = _cur_dpi;
1473 n->zoom = ZOOM_LVL_NORMAL;
1475 assert(width > 0);
1476 assert(height > 0);
1478 if ((left -= o->left) < 0) {
1479 width += left;
1480 if (width <= 0) return false;
1481 n->left = -left;
1482 left = 0;
1483 } else {
1484 n->left = 0;
1487 if (width > o->width - left) {
1488 width = o->width - left;
1489 if (width <= 0) return false;
1491 n->width = width;
1493 if ((top -= o->top) < 0) {
1494 height += top;
1495 if (height <= 0) return false;
1496 n->top = -top;
1497 top = 0;
1498 } else {
1499 n->top = 0;
1502 n->dst_ptr = blitter->MoveTo(o->dst_ptr, left, top);
1503 n->pitch = o->pitch;
1505 if (height > o->height - top) {
1506 height = o->height - top;
1507 if (height <= 0) return false;
1509 n->height = height;
1511 return true;
1515 * Update cursor dimension.
1516 * Called when changing cursor sprite resp. reloading grfs.
1518 void UpdateCursorSize()
1520 CursorVars *cv = &_cursor;
1521 const Sprite *p = GetSprite(GB(cv->sprite, 0, SPRITE_WIDTH), ST_NORMAL);
1523 cv->size.y = UnScaleGUI(p->height);
1524 cv->size.x = UnScaleGUI(p->width);
1525 cv->offs.x = UnScaleGUI(p->x_offs);
1526 cv->offs.y = UnScaleGUI(p->y_offs);
1528 cv->dirty = true;
1532 * Switch cursor to different sprite.
1533 * @param cursor Sprite to draw for the cursor.
1534 * @param pal Palette to use for recolouring.
1536 static void SetCursorSprite(CursorID cursor, PaletteID pal)
1538 CursorVars *cv = &_cursor;
1539 if (cv->sprite == cursor) return;
1541 cv->sprite = cursor;
1542 cv->pal = pal;
1543 UpdateCursorSize();
1545 cv->short_vehicle_offset = 0;
1548 static void SwitchAnimatedCursor()
1550 const AnimCursor *cur = _cursor.animate_cur;
1552 if (cur == NULL || cur->sprite == AnimCursor::LAST) cur = _cursor.animate_list;
1554 SetCursorSprite(cur->sprite, _cursor.pal);
1556 _cursor.animate_timeout = cur->display_time;
1557 _cursor.animate_cur = cur + 1;
1560 void CursorTick()
1562 if (_cursor.animate_timeout != 0 && --_cursor.animate_timeout == 0) {
1563 SwitchAnimatedCursor();
1568 * Assign a single non-animated sprite to the cursor.
1569 * @param sprite Sprite to draw for the cursor.
1570 * @param pal Palette to use for recolouring.
1571 * @see SetAnimatedMouseCursor
1573 void SetMouseCursor(CursorID sprite, PaletteID pal)
1575 /* Turn off animation */
1576 _cursor.animate_timeout = 0;
1577 /* Set cursor */
1578 SetCursorSprite(sprite, pal);
1582 * Assign an animation to the cursor.
1583 * @param table Array of animation states.
1584 * @see SetMouseCursor
1586 void SetAnimatedMouseCursor(const AnimCursor *table)
1588 _cursor.animate_list = table;
1589 _cursor.animate_cur = NULL;
1590 _cursor.pal = PAL_NONE;
1591 SwitchAnimatedCursor();
1595 * Update cursor position on mouse movement.
1596 * @param x New X position.
1597 * @param y New Y position.
1598 * @param queued True, if the OS queues mouse warps after pending mouse movement events.
1599 * False, if the warp applies instantaneous.
1600 * @return true, if the OS cursor position should be warped back to this->pos.
1602 bool CursorVars::UpdateCursorPosition(int x, int y, bool queued_warp)
1604 /* Detecting relative mouse movement is somewhat tricky.
1605 * - There may be multiple mouse move events in the video driver queue (esp. when OpenTTD lags a bit).
1606 * - When we request warping the mouse position (return true), a mouse move event is appended at the end of the queue.
1608 * So, when this->fix_at is active, we use the following strategy:
1609 * - The first movement triggers the warp to reset the mouse position.
1610 * - Subsequent events have to compute movement relative to the previous event.
1611 * - The relative movement is finished, when we receive the event matching the warp.
1614 if (x == this->pos.x && y == this->pos.y) {
1615 /* Warp finished. */
1616 this->queued_warp = false;
1619 this->delta.x = x - (this->queued_warp ? this->last_position.x : this->pos.x);
1620 this->delta.y = y - (this->queued_warp ? this->last_position.y : this->pos.y);
1622 this->last_position.x = x;
1623 this->last_position.y = y;
1625 bool need_warp = false;
1626 if (this->fix_at) {
1627 if (this->delta.x != 0 || this->delta.y != 0) {
1628 /* Trigger warp.
1629 * Note: We also trigger warping again, if there is already a pending warp.
1630 * This makes it more tolerant about the OS or other software inbetween
1631 * botchering the warp. */
1632 this->queued_warp = queued_warp;
1633 need_warp = true;
1635 } else if (this->pos.x != x || this->pos.y != y) {
1636 this->queued_warp = false; // Cancel warping, we are no longer confining the position.
1637 this->dirty = true;
1638 this->pos.x = x;
1639 this->pos.y = y;
1641 return need_warp;
1644 bool ChangeResInGame(int width, int height)
1646 return (_screen.width == width && _screen.height == height) || VideoDriver::GetActiveDriver()->ChangeResolution(width, height);
1649 bool ToggleFullScreen(bool fs)
1651 bool result = VideoDriver::GetActiveDriver()->ToggleFullscreen(fs);
1652 if (_fullscreen != fs && _num_resolutions == 0) {
1653 DEBUG(driver, 0, "Could not find a suitable fullscreen resolution");
1655 return result;
1658 static int CDECL compare_res(const Dimension *pa, const Dimension *pb)
1660 int x = pa->width - pb->width;
1661 if (x != 0) return x;
1662 return pa->height - pb->height;
1665 void SortResolutions(int count)
1667 QSortT(_resolutions, count, &compare_res);