Extend object variable 0x60 to also return the view
[openttd/fttd.git] / src / gfx.cpp
bloba6ab37a23fa57899fd2ef27b519b9512a8d05bbd
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/sprites.h"
27 #include "table/control_codes.h"
29 byte _dirkeys; ///< 1 = left, 2 = up, 4 = right, 8 = down
30 bool _fullscreen;
31 CursorVars _cursor;
32 bool _ctrl_pressed; ///< Is Ctrl pressed?
33 bool _shift_pressed; ///< Is Shift pressed?
34 byte _fast_forward;
35 bool _left_button_down; ///< Is left mouse button pressed?
36 bool _left_button_clicked; ///< Is left mouse button clicked?
37 bool _right_button_down; ///< Is right mouse button pressed?
38 bool _right_button_clicked; ///< Is right mouse button clicked?
39 DrawPixelInfo _screen;
40 bool _screen_disable_anim = false; ///< Disable palette animation (important for 32bpp-anim blitter during giant screenshot)
41 bool _exit_game;
42 GameMode _game_mode;
43 SwitchMode _switch_mode; ///< The next mainloop command.
44 PauseModeByte _pause_mode;
45 Palette _cur_palette;
47 static byte _stringwidth_table[FS_END][224]; ///< Cache containing width of often used characters. @see GetCharacterWidth()
48 DrawPixelInfo *_cur_dpi;
49 byte _colour_gradient[COLOUR_END][8];
51 static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL, SpriteID sprite_id = SPR_CURSOR_MOUSE);
52 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);
54 static ReusableBuffer<uint8> _cursor_backup;
56 /**
57 * The rect for repaint.
59 * This rectangle defines the area which should be repaint by the video driver.
61 * @ingroup dirty
63 static Rect _invalid_rect;
64 static const byte *_colour_remap_ptr;
65 static byte _string_colourremap[3]; ///< Recoloursprite for stringdrawing. The grf loader ensures that #ST_FONT sprites only use colours 0 to 2.
67 static const uint DIRTY_BLOCK_HEIGHT = 8;
68 static const uint DIRTY_BLOCK_WIDTH = 64;
70 static uint _dirty_bytes_per_line = 0;
71 static byte *_dirty_blocks = NULL;
72 extern uint _dirty_block_colour;
74 void GfxScroll(int left, int top, int width, int height, int xo, int yo)
76 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
78 if (xo == 0 && yo == 0) return;
80 if (_cursor.visible) UndrawMouseCursor();
82 #ifdef ENABLE_NETWORK
83 if (_networking) NetworkUndrawChatMessage();
84 #endif /* ENABLE_NETWORK */
86 blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo);
87 /* This part of the screen is now dirty. */
88 _video_driver->MakeDirty(left, top, width, height);
92 /**
93 * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
95 * @pre dpi->zoom == ZOOM_LVL_NORMAL, right >= left, bottom >= top
96 * @param left Minimum X (inclusive)
97 * @param top Minimum Y (inclusive)
98 * @param right Maximum X (inclusive)
99 * @param bottom Maximum Y (inclusive)
100 * @param colour A 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR)
101 * @param mode
102 * FILLRECT_OPAQUE: Fill the rectangle with the specified colour
103 * FILLRECT_CHECKER: Like FILLRECT_OPAQUE, but only draw every second pixel (used to grey out things)
104 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the rectangle currently on screen
106 void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
108 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
109 const DrawPixelInfo *dpi = _cur_dpi;
110 void *dst;
111 const int otop = top;
112 const int oleft = left;
114 if (dpi->zoom != ZOOM_LVL_NORMAL) return;
115 if (left > right || top > bottom) return;
116 if (right < dpi->left || left >= dpi->left + dpi->width) return;
117 if (bottom < dpi->top || top >= dpi->top + dpi->height) return;
119 if ( (left -= dpi->left) < 0) left = 0;
120 right = right - dpi->left + 1;
121 if (right > dpi->width) right = dpi->width;
122 right -= left;
123 assert(right > 0);
125 if ( (top -= dpi->top) < 0) top = 0;
126 bottom = bottom - dpi->top + 1;
127 if (bottom > dpi->height) bottom = dpi->height;
128 bottom -= top;
129 assert(bottom > 0);
131 dst = blitter->MoveTo(dpi->dst_ptr, left, top);
133 switch (mode) {
134 default: // FILLRECT_OPAQUE
135 blitter->DrawRect(dst, right, bottom, (uint8)colour);
136 break;
138 case FILLRECT_RECOLOUR:
139 blitter->DrawColourMappingRect(dst, right, bottom, GB(colour, 0, PALETTE_WIDTH));
140 break;
142 case FILLRECT_CHECKER: {
143 byte bo = (oleft - left + dpi->left + otop - top + dpi->top) & 1;
144 do {
145 for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, (uint8)colour);
146 dst = blitter->MoveTo(dst, 0, 1);
147 } while (--bottom > 0);
148 break;
154 * Check line clipping by using a linear equation and draw the visible part of
155 * the line given by x/y and x2/y2.
156 * @param video Destination pointer to draw into.
157 * @param x X coordinate of first point.
158 * @param y Y coordinate of first point.
159 * @param x2 X coordinate of second point.
160 * @param y2 Y coordinate of second point.
161 * @param screen_width With of the screen to check clipping against.
162 * @param screen_height Height of the screen to check clipping against.
163 * @param colour Colour of the line.
164 * @param width Width of the line.
165 * @param dash Length of dashes for dashed lines. 0 means solid line.
167 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)
169 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
171 assert(width > 0);
173 if (y2 == y) {
174 /* Special case: horizontal line. */
175 blitter->DrawLine(video,
176 Clamp(x, 0, screen_width), y,
177 Clamp(x2, 0, screen_width), y2,
178 screen_width, screen_height, colour, width, dash);
179 return;
181 if (x2 == x) {
182 /* Special case: vertical line. */
183 blitter->DrawLine(video,
184 x, Clamp(y, 0, screen_height),
185 x2, Clamp(y2, 0, screen_height),
186 screen_width, screen_height, colour, width, dash);
187 return;
190 int grade_y = y2 - y;
191 int grade_x = x2 - x;
193 /* prevent integer overflows. */
194 int margin = 1;
195 while (INT_MAX / abs(grade_y) < max(abs(x), abs(screen_width - x))) {
196 grade_y /= 2;
197 grade_x /= 2;
198 margin *= 2; // account for rounding errors
201 /* If the line is outside the screen on the same side at X positions 0
202 * and screen_width, we don't need to draw anything. */
203 int offset_0 = y - x * grade_y / grade_x;
204 int offset_width = y + (screen_width - x) * grade_y / grade_x;
205 if ((offset_0 > screen_height + width / 2 + margin && offset_width > screen_height + width / 2 + margin) ||
206 (offset_0 < -width / 2 - margin && offset_width < -width / 2 - margin)) {
207 return;
210 /* It is possible to use the line equation to further reduce the amount of
211 * work the blitter has to do by shortening the effective line segment.
212 * However, in order to get that right and prevent the flickering effects
213 * of rounding errors so much additional code has to be run here that in
214 * the general case the effect is not noticable. */
216 blitter->DrawLine(video, x, y, x2, y2, screen_width, screen_height, colour, width, dash);
220 * Align parameters of a line to the given DPI and check simple clipping.
221 * @param dpi Screen parameters to align with.
222 * @param x X coordinate of first point.
223 * @param y Y coordinate of first point.
224 * @param x2 X coordinate of second point.
225 * @param y2 Y coordinate of second point.
226 * @param width Width of the line.
227 * @return True if the line is likely to be visible, false if it's certainly
228 * invisible.
230 static inline bool GfxPreprocessLine(DrawPixelInfo *dpi, int &x, int &y, int &x2, int &y2, int width)
232 x -= dpi->left;
233 x2 -= dpi->left;
234 y -= dpi->top;
235 y2 -= dpi->top;
237 /* Check simple clipping */
238 if (x + width / 2 < 0 && x2 + width / 2 < 0 ) return false;
239 if (y + width / 2 < 0 && y2 + width / 2 < 0 ) return false;
240 if (x - width / 2 > dpi->width && x2 - width / 2 > dpi->width ) return false;
241 if (y - width / 2 > dpi->height && y2 - width / 2 > dpi->height) return false;
242 return true;
245 void GfxDrawLine(int x, int y, int x2, int y2, int colour, int width, int dash)
247 DrawPixelInfo *dpi = _cur_dpi;
248 if (GfxPreprocessLine(dpi, x, y, x2, y2, width)) {
249 GfxDoDrawLine(dpi->dst_ptr, x, y, x2, y2, dpi->width, dpi->height, colour, width, dash);
253 void GfxDrawLineUnscaled(int x, int y, int x2, int y2, int colour)
255 DrawPixelInfo *dpi = _cur_dpi;
256 if (GfxPreprocessLine(dpi, x, y, x2, y2, 1)) {
257 GfxDoDrawLine(dpi->dst_ptr,
258 UnScaleByZoom(x, dpi->zoom), UnScaleByZoom(y, dpi->zoom),
259 UnScaleByZoom(x2, dpi->zoom), UnScaleByZoom(y2, dpi->zoom),
260 UnScaleByZoom(dpi->width, dpi->zoom), UnScaleByZoom(dpi->height, dpi->zoom), colour, 1);
265 * Draws the projection of a parallelepiped.
266 * This can be used to draw boxes in world coordinates.
268 * @param x Screen X-coordinate of top front corner.
269 * @param y Screen Y-coordinate of top front corner.
270 * @param dx1 Screen X-length of first edge.
271 * @param dy1 Screen Y-length of first edge.
272 * @param dx2 Screen X-length of second edge.
273 * @param dy2 Screen Y-length of second edge.
274 * @param dx3 Screen X-length of third edge.
275 * @param dy3 Screen Y-length of third edge.
277 void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3)
279 /* ....
280 * .. ....
281 * .. ....
282 * .. ^
283 * <--__(dx1,dy1) /(dx2,dy2)
284 * : --__ / :
285 * : --__ / :
286 * : *(x,y) :
287 * : | :
288 * : | ..
289 * .... |(dx3,dy3)
290 * .... | ..
291 * ....V.
294 static const byte colour = PC_WHITE;
296 GfxDrawLineUnscaled(x, y, x + dx1, y + dy1, colour);
297 GfxDrawLineUnscaled(x, y, x + dx2, y + dy2, colour);
298 GfxDrawLineUnscaled(x, y, x + dx3, y + dy3, colour);
300 GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx2, y + dy1 + dy2, colour);
301 GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx3, y + dy1 + dy3, colour);
302 GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx1, y + dy2 + dy1, colour);
303 GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx3, y + dy2 + dy3, colour);
304 GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx1, y + dy3 + dy1, colour);
305 GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx2, y + dy3 + dy2, colour);
309 * Set the colour remap to be for the given colour.
310 * @param colour the new colour of the remap.
312 static void SetColourRemap(TextColour colour)
314 if (colour == TC_INVALID) return;
316 /* Black strings have no shading ever; the shading is black, so it
317 * would be invisible at best, but it actually makes it illegible. */
318 bool no_shade = (colour & TC_NO_SHADE) != 0 || colour == TC_BLACK;
319 bool raw_colour = (colour & TC_IS_PALETTE_COLOUR) != 0;
320 colour &= ~(TC_NO_SHADE | TC_IS_PALETTE_COLOUR);
322 _string_colourremap[1] = raw_colour ? (byte)colour : _string_colourmap[colour];
323 _string_colourremap[2] = no_shade ? 0 : 1;
324 _colour_remap_ptr = _string_colourremap;
328 * Drawing routine for drawing a laid out line of text.
329 * @param line String to draw.
330 * @param y The top most position to draw on.
331 * @param left The left most position to draw on.
332 * @param right The right most position to draw on.
333 * @param align The alignment of the string when drawing left-to-right. In the
334 * case a right-to-left language is chosen this is inverted so it
335 * will be drawn in the right direction.
336 * @param underline Whether to underline what has been drawn or not.
337 * @param truncation Whether to perform string truncation or not.
339 * @return In case of left or center alignment the right most pixel we have drawn to.
340 * In case of right alignment the left most pixel we have drawn to.
342 static int DrawLayoutLine(const ParagraphLayouter::Line *line, int y, int left, int right, StringAlignment align, bool underline, bool truncation)
344 if (line->CountRuns() == 0) return 0;
346 int w = line->GetWidth();
347 int h = line->GetLeading();
350 * The following is needed for truncation.
351 * Depending on the text direction, we either remove bits at the rear
352 * or the front. For this we shift the entire area to draw so it fits
353 * within the left/right bounds and the side we do not truncate it on.
354 * Then we determine the truncation location, i.e. glyphs that fall
355 * outside of the range min_x - max_x will not be drawn; they are thus
356 * the truncated glyphs.
358 * At a later step we insert the dots.
361 int max_w = right - left + 1; // The maximum width.
363 int offset_x = 0; // The offset we need for positioning the glyphs
364 int min_x = left; // The minimum x position to draw normal glyphs on.
365 int max_x = right; // The maximum x position to draw normal glyphs on.
367 truncation &= max_w < w; // Whether we need to do truncation.
368 int dot_width = 0; // Cache for the width of the dot.
369 const Sprite *dot_sprite = NULL; // Cache for the sprite of the dot.
371 if (truncation) {
373 * Assumption may be made that all fonts of a run are of the same size.
374 * In any case, we'll use these dots for the abbreviation, so even if
375 * another size would be chosen it won't have truncated too little for
376 * the truncation dots.
378 FontCache *fc = ((const Font*)line->GetVisualRun(0)->GetFont())->fc;
379 GlyphID dot_glyph = fc->MapCharToGlyph('.');
380 dot_width = fc->GetGlyphWidth(dot_glyph);
381 dot_sprite = fc->GetGlyph(dot_glyph);
383 if (_current_text_dir == TD_RTL) {
384 min_x += 3 * dot_width;
385 offset_x = w - 3 * dot_width - max_w;
386 } else {
387 max_x -= 3 * dot_width;
390 w = max_w;
393 /* In case we have a RTL language we swap the alignment. */
394 if (!(align & SA_FORCE) && _current_text_dir == TD_RTL && (align & SA_HOR_MASK) != SA_HOR_CENTER) align ^= SA_RIGHT;
396 /* right is the right most position to draw on. In this case we want to do
397 * calculations with the width of the string. In comparison right can be
398 * seen as lastof(todraw) and width as lengthof(todraw). They differ by 1.
399 * So most +1/-1 additions are to move from lengthof to 'indices'.
401 switch (align & SA_HOR_MASK) {
402 case SA_LEFT:
403 /* right + 1 = left + w */
404 right = left + w - 1;
405 break;
407 case SA_HOR_CENTER:
408 left = RoundDivSU(right + 1 + left - w, 2);
409 /* right + 1 = left + w */
410 right = left + w - 1;
411 break;
413 case SA_RIGHT:
414 left = right + 1 - w;
415 break;
417 default:
418 NOT_REACHED();
421 for (int run_index = 0; run_index < line->CountRuns(); run_index++) {
422 const ParagraphLayouter::VisualRun *run = line->GetVisualRun(run_index);
423 const Font *f = (const Font*)run->GetFont();
425 FontCache *fc = f->fc;
426 TextColour colour = f->colour;
427 SetColourRemap(colour);
429 DrawPixelInfo *dpi = _cur_dpi;
430 int dpi_left = dpi->left;
431 int dpi_right = dpi->left + dpi->width - 1;
433 bool draw_shadow = fc->GetDrawGlyphShadow() && colour != TC_BLACK;
435 for (int i = 0; i < run->GetGlyphCount(); i++) {
436 GlyphID glyph = run->GetGlyphs()[i];
438 /* Not a valid glyph (empty) */
439 if (glyph == 0xFFFF) continue;
441 int begin_x = (int)run->GetPositions()[i * 2] + left - offset_x;
442 int end_x = (int)run->GetPositions()[i * 2 + 2] + left - offset_x - 1;
443 int top = (int)run->GetPositions()[i * 2 + 1] + y;
445 /* Truncated away. */
446 if (truncation && (begin_x < min_x || end_x > max_x)) continue;
448 const Sprite *sprite = fc->GetGlyph(glyph);
449 /* Check clipping (the "+ 1" is for the shadow). */
450 if (begin_x + sprite->x_offs > dpi_right || begin_x + sprite->x_offs + sprite->width /* - 1 + 1 */ < dpi_left) continue;
452 if (draw_shadow && (glyph & SPRITE_GLYPH) == 0) {
453 SetColourRemap(TC_BLACK);
454 GfxMainBlitter(sprite, begin_x + 1, top + 1, BM_COLOUR_REMAP);
455 SetColourRemap(colour);
457 GfxMainBlitter(sprite, begin_x, top, BM_COLOUR_REMAP);
461 if (truncation) {
462 int x = (_current_text_dir == TD_RTL) ? left : (right - 3 * dot_width);
463 for (int i = 0; i < 3; i++, x += dot_width) {
464 GfxMainBlitter(dot_sprite, x, y, BM_COLOUR_REMAP);
468 if (underline) {
469 GfxFillRect(left, y + h, right, y + h, _string_colourremap[1]);
472 return (align & SA_HOR_MASK) == SA_RIGHT ? left : right;
476 * Draw string, possibly truncated to make it fit in its allocated space
478 * @param left The left most position to draw on.
479 * @param right The right most position to draw on.
480 * @param top The top most position to draw on.
481 * @param str String to draw.
482 * @param colour Colour used for drawing the string, see DoDrawString() for details
483 * @param align The alignment of the string when drawing left-to-right. In the
484 * case a right-to-left language is chosen this is inverted so it
485 * will be drawn in the right direction.
486 * @param underline Whether to underline what has been drawn or not.
487 * @param fontsize The size of the initial characters.
488 * @return In case of left or center alignment the right most pixel we have drawn to.
489 * In case of right alignment the left most pixel we have drawn to.
491 int DrawString(int left, int right, int top, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
493 /* The string may contain control chars to change the font, just use the biggest font for clipping. */
494 int max_height = max(max(FONT_HEIGHT_SMALL, FONT_HEIGHT_NORMAL), max(FONT_HEIGHT_LARGE, FONT_HEIGHT_MONO));
496 /* Funny glyphs may extent outside the usual bounds, so relax the clipping somewhat. */
497 int extra = max_height / 2;
499 if (_cur_dpi->top + _cur_dpi->height + extra < top || _cur_dpi->top > top + max_height + extra ||
500 _cur_dpi->left + _cur_dpi->width + extra < left || _cur_dpi->left > right + extra) {
501 return 0;
504 Layouter layout(str, INT32_MAX, colour, fontsize);
505 if (layout.Length() == 0) return 0;
507 return DrawLayoutLine(*layout.Begin(), top, left, right, align, underline, true);
511 * Draw string, possibly truncated to make it fit in its allocated space
513 * @param left The left most position to draw on.
514 * @param right The right most position to draw on.
515 * @param top The top most position to draw on.
516 * @param str String to draw.
517 * @param colour Colour used for drawing the string, see DoDrawString() for details
518 * @param align The alignment of the string when drawing left-to-right. In the
519 * case a right-to-left language is chosen this is inverted so it
520 * will be drawn in the right direction.
521 * @param underline Whether to underline what has been drawn or not.
522 * @param fontsize The size of the initial characters.
523 * @return In case of left or center alignment the right most pixel we have drawn to.
524 * In case of right alignment the left most pixel we have drawn to.
526 int DrawString(int left, int right, int top, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
528 char buffer[DRAW_STRING_BUFFER];
529 GetString(buffer, str, lastof(buffer));
530 return DrawString(left, right, top, buffer, colour, align, underline, fontsize);
534 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
535 * @param str string to check
536 * @param maxw maximum string width
537 * @return height of pixels of string when it is drawn
539 int GetStringHeight(const char *str, int maxw, FontSize fontsize)
541 Layouter layout(str, maxw, TC_FROMSTRING, fontsize);
542 return layout.GetBounds().height;
546 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
547 * @param str string to check
548 * @param maxw maximum string width
549 * @return height of pixels of string when it is drawn
551 int GetStringHeight(StringID str, int maxw)
553 char buffer[DRAW_STRING_BUFFER];
554 GetString(buffer, str, lastof(buffer));
555 return GetStringHeight(buffer, maxw);
559 * Calculates number of lines of string. The string is changed to a multiline string if needed.
560 * @param str string to check
561 * @param maxw maximum string width
562 * @return number of lines of string when it is drawn
564 int GetStringLineCount(StringID str, int maxw)
566 char buffer[DRAW_STRING_BUFFER];
567 GetString(buffer, str, lastof(buffer));
569 Layouter layout(buffer, maxw);
570 return layout.Length();
574 * Calculate string bounding box for multi-line strings.
575 * @param str String to check.
576 * @param suggestion Suggested bounding box.
577 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
579 Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestion)
581 Dimension box = {suggestion.width, GetStringHeight(str, suggestion.width)};
582 return box;
586 * Calculate string bounding box for multi-line strings.
587 * @param str String to check.
588 * @param suggestion Suggested bounding box.
589 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
591 Dimension GetStringMultiLineBoundingBox(const char *str, const Dimension &suggestion)
593 Dimension box = {suggestion.width, GetStringHeight(str, suggestion.width)};
594 return box;
598 * Draw string, possibly over multiple lines.
600 * @param left The left most position to draw on.
601 * @param right The right most position to draw on.
602 * @param top The top most position to draw on.
603 * @param bottom The bottom most position to draw on.
604 * @param str String to draw.
605 * @param colour Colour used for drawing the string, see DoDrawString() for details
606 * @param align The horizontal and vertical alignment of the string.
607 * @param underline Whether to underline all strings
608 * @param fontsize The size of the initial characters.
610 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
612 int DrawStringMultiLine(int left, int right, int top, int bottom, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
614 int maxw = right - left + 1;
615 int maxh = bottom - top + 1;
617 /* It makes no sense to even try if it can't be drawn anyway, or
618 * do we really want to support fonts of 0 or less pixels high? */
619 if (maxh <= 0) return top;
621 Layouter layout(str, maxw, colour, fontsize);
622 int total_height = layout.GetBounds().height;
623 int y;
624 switch (align & SA_VERT_MASK) {
625 case SA_TOP:
626 y = top;
627 break;
629 case SA_VERT_CENTER:
630 y = RoundDivSU(bottom + top - total_height, 2);
631 break;
633 case SA_BOTTOM:
634 y = bottom - total_height;
635 break;
637 default: NOT_REACHED();
640 int last_line = top;
641 int first_line = bottom;
643 for (const ParagraphLayouter::Line **iter = layout.Begin(); iter != layout.End(); iter++) {
644 const ParagraphLayouter::Line *line = *iter;
646 int line_height = line->GetLeading();
647 if (y >= top && y < bottom) {
648 last_line = y + line_height;
649 if (first_line > y) first_line = y;
651 DrawLayoutLine(line, y, left, right, align, underline, false);
653 y += line_height;
656 return ((align & SA_VERT_MASK) == SA_BOTTOM) ? first_line : last_line;
660 * Draw string, possibly over multiple lines.
662 * @param left The left most position to draw on.
663 * @param right The right most position to draw on.
664 * @param top The top most position to draw on.
665 * @param bottom The bottom most position to draw on.
666 * @param str String to draw.
667 * @param colour Colour used for drawing the string, see DoDrawString() for details
668 * @param align The horizontal and vertical alignment of the string.
669 * @param underline Whether to underline all strings
670 * @param fontsize The size of the initial characters.
672 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
674 int DrawStringMultiLine(int left, int right, int top, int bottom, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
676 char buffer[DRAW_STRING_BUFFER];
677 GetString(buffer, str, lastof(buffer));
678 return DrawStringMultiLine(left, right, top, bottom, buffer, colour, align, underline, fontsize);
682 * Return the string dimension in pixels. The height and width are returned
683 * in a single Dimension value. TINYFONT, BIGFONT modifiers are only
684 * supported as the first character of the string. The returned dimensions
685 * are therefore a rough estimation correct for all the current strings
686 * but not every possible combination
687 * @param str string to calculate pixel-width
688 * @param start_fontsize Fontsize to start the text with
689 * @return string width and height in pixels
691 Dimension GetStringBoundingBox(const char *str, FontSize start_fontsize)
693 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
694 return layout.GetBounds();
698 * Get bounding box of a string. Uses parameters set by #DParam if needed.
699 * Has the same restrictions as #GetStringBoundingBox(const char *str).
700 * @param strid String to examine.
701 * @return Width and height of the bounding box for the string in pixels.
703 Dimension GetStringBoundingBox(StringID strid)
705 char buffer[DRAW_STRING_BUFFER];
707 GetString(buffer, strid, lastof(buffer));
708 return GetStringBoundingBox(buffer);
712 * Get the leading corner of a character in a single-line string relative
713 * to the start of the string.
714 * @param str String containing the character.
715 * @param ch Pointer to the character in the string.
716 * @param start_fontsize Font size to start the text with.
717 * @return Upper left corner of the glyph associated with the character.
719 Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsize)
721 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
722 return layout.GetCharPosition(ch);
726 * Get the character from a string that is drawn at a specific position.
727 * @param str String to test.
728 * @param x Position relative to the start of the string.
729 * @param start_fontsize Font size to start the text with.
730 * @return Pointer to the character at the position or NULL if there is no character at the position.
732 const char *GetCharAtPosition(const char *str, int x, FontSize start_fontsize)
734 if (x < 0) return NULL;
736 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
737 return layout.GetCharAtPosition(x);
741 * Draw single character horizontally centered around (x,y)
742 * @param c Character (glyph) to draw
743 * @param x X position to draw character
744 * @param y Y position to draw character
745 * @param colour Colour to use, see DoDrawString() for details
747 void DrawCharCentered(WChar c, int x, int y, TextColour colour)
749 SetColourRemap(colour);
750 GfxMainBlitter(GetGlyph(FS_NORMAL, c), x - GetCharacterWidth(FS_NORMAL, c) / 2, y, BM_COLOUR_REMAP);
754 * Get the size of a sprite.
755 * @param sprid Sprite to examine.
756 * @param [out] offset Optionally returns the sprite position offset.
757 * @return Sprite size in pixels.
758 * @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.
760 Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
762 const Sprite *sprite = GetSprite(sprid, ST_NORMAL);
764 if (offset != NULL) {
765 offset->x = UnScaleByZoom(sprite->x_offs, zoom);
766 offset->y = UnScaleByZoom(sprite->y_offs, zoom);
769 Dimension d;
770 d.width = max<int>(0, UnScaleByZoom(sprite->x_offs + sprite->width, zoom));
771 d.height = max<int>(0, UnScaleByZoom(sprite->y_offs + sprite->height, zoom));
772 return d;
776 * Draw a sprite in a viewport.
777 * @param img Image number to draw
778 * @param pal Palette to use.
779 * @param x Left coordinate of image in viewport, scaled by zoom
780 * @param y Top coordinate of image in viewport, scaled by zoom
781 * @param sub If available, draw only specified part of the sprite
783 void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub)
785 SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
786 if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
787 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
788 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite);
789 } else if (pal != PAL_NONE) {
790 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
791 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_COLOUR_REMAP, sub, real_sprite);
792 } else {
793 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite);
798 * Draw a sprite, not in a viewport
799 * @param img Image number to draw
800 * @param pal Palette to use.
801 * @param x Left coordinate of image in pixels
802 * @param y Top coordinate of image in pixels
803 * @param sub If available, draw only specified part of the sprite
804 * @param zoom Zoom level of sprite
806 void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub, ZoomLevel zoom)
808 SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
809 if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
810 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
811 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite, zoom);
812 } else if (pal != PAL_NONE) {
813 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
814 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_COLOUR_REMAP, sub, real_sprite, zoom);
815 } else {
816 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite, zoom);
821 * The code for setting up the blitter mode and sprite information before finally drawing the sprite.
822 * @param sprite The sprite to draw.
823 * @param x The X location to draw.
824 * @param y The Y location to draw.
825 * @param mode The settings for the blitter to pass.
826 * @param sub Whether to only draw a sub set of the sprite.
827 * @param zoom The zoom level at which to draw the sprites.
828 * @tparam ZOOM_BASE The factor required to get the sub sprite information into the right size.
829 * @tparam SCALED_XY Whether the X and Y are scaled or unscaled.
831 template <int ZOOM_BASE, bool SCALED_XY>
832 static void GfxBlitter(const Sprite * const sprite, int x, int y, BlitterMode mode, const SubSprite * const sub, SpriteID sprite_id, ZoomLevel zoom)
834 const DrawPixelInfo *dpi = _cur_dpi;
835 Blitter::BlitterParams bp;
837 if (SCALED_XY) {
838 /* Scale it */
839 x = ScaleByZoom(x, zoom);
840 y = ScaleByZoom(y, zoom);
843 /* Move to the correct offset */
844 x += sprite->x_offs;
845 y += sprite->y_offs;
847 if (sub == NULL) {
848 /* No clipping. */
849 bp.skip_left = 0;
850 bp.skip_top = 0;
851 bp.width = UnScaleByZoom(sprite->width, zoom);
852 bp.height = UnScaleByZoom(sprite->height, zoom);
853 } else {
854 /* Amount of pixels to clip from the source sprite */
855 int clip_left = max(0, -sprite->x_offs + sub->left * ZOOM_BASE );
856 int clip_top = max(0, -sprite->y_offs + sub->top * ZOOM_BASE );
857 int clip_right = max(0, sprite->width - (-sprite->x_offs + (sub->right + 1) * ZOOM_BASE));
858 int clip_bottom = max(0, sprite->height - (-sprite->y_offs + (sub->bottom + 1) * ZOOM_BASE));
860 if (clip_left + clip_right >= sprite->width) return;
861 if (clip_top + clip_bottom >= sprite->height) return;
863 bp.skip_left = UnScaleByZoomLower(clip_left, zoom);
864 bp.skip_top = UnScaleByZoomLower(clip_top, zoom);
865 bp.width = UnScaleByZoom(sprite->width - clip_left - clip_right, zoom);
866 bp.height = UnScaleByZoom(sprite->height - clip_top - clip_bottom, zoom);
868 x += ScaleByZoom(bp.skip_left, zoom);
869 y += ScaleByZoom(bp.skip_top, zoom);
872 /* Copy the main data directly from the sprite */
873 bp.sprite = sprite->data;
874 bp.sprite_width = sprite->width;
875 bp.sprite_height = sprite->height;
876 bp.top = 0;
877 bp.left = 0;
879 bp.dst = dpi->dst_ptr;
880 bp.pitch = dpi->pitch;
881 bp.remap = _colour_remap_ptr;
883 assert(sprite->width > 0);
884 assert(sprite->height > 0);
886 if (bp.width <= 0) return;
887 if (bp.height <= 0) return;
889 y -= SCALED_XY ? ScaleByZoom(dpi->top, zoom) : dpi->top;
890 int y_unscaled = UnScaleByZoom(y, zoom);
891 /* Check for top overflow */
892 if (y < 0) {
893 bp.height -= -y_unscaled;
894 if (bp.height <= 0) return;
895 bp.skip_top += -y_unscaled;
896 y = 0;
897 } else {
898 bp.top = y_unscaled;
901 /* Check for bottom overflow */
902 y += SCALED_XY ? ScaleByZoom(bp.height - dpi->height, zoom) : ScaleByZoom(bp.height, zoom) - dpi->height;
903 if (y > 0) {
904 bp.height -= UnScaleByZoom(y, zoom);
905 if (bp.height <= 0) return;
908 x -= SCALED_XY ? ScaleByZoom(dpi->left, zoom) : dpi->left;
909 int x_unscaled = UnScaleByZoom(x, zoom);
910 /* Check for left overflow */
911 if (x < 0) {
912 bp.width -= -x_unscaled;
913 if (bp.width <= 0) return;
914 bp.skip_left += -x_unscaled;
915 x = 0;
916 } else {
917 bp.left = x_unscaled;
920 /* Check for right overflow */
921 x += SCALED_XY ? ScaleByZoom(bp.width - dpi->width, zoom) : ScaleByZoom(bp.width, zoom) - dpi->width;
922 if (x > 0) {
923 bp.width -= UnScaleByZoom(x, zoom);
924 if (bp.width <= 0) return;
927 assert(bp.skip_left + bp.width <= UnScaleByZoom(sprite->width, zoom));
928 assert(bp.skip_top + bp.height <= UnScaleByZoom(sprite->height, zoom));
930 /* We do not want to catch the mouse. However we also use that spritenumber for unknown (text) sprites. */
931 if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && sprite_id != SPR_CURSOR_MOUSE) {
932 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
933 void *topleft = blitter->MoveTo(bp.dst, bp.left, bp.top);
934 void *bottomright = blitter->MoveTo(topleft, bp.width - 1, bp.height - 1);
936 void *clicked = _newgrf_debug_sprite_picker.clicked_pixel;
938 if (topleft <= clicked && clicked <= bottomright) {
939 uint offset = (((size_t)clicked - (size_t)topleft) / (blitter->GetScreenDepth() / 8)) % bp.pitch;
940 if (offset < (uint)bp.width) {
941 _newgrf_debug_sprite_picker.sprites.Include(sprite_id);
946 BlitterFactory::GetCurrentBlitter()->Draw(&bp, mode, zoom);
949 static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id)
951 GfxBlitter<ZOOM_LVL_BASE, false>(sprite, x, y, mode, sub, sprite_id, _cur_dpi->zoom);
954 static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id, ZoomLevel zoom)
956 GfxBlitter<1, true>(sprite, x, y, mode, sub, sprite_id, zoom);
959 void DoPaletteAnimations();
961 void GfxInitPalettes()
963 memcpy(&_cur_palette, &_palette, sizeof(_cur_palette));
964 DoPaletteAnimations();
967 #define EXTR(p, q) (((uint16)(palette_animation_counter * (p)) * (q)) >> 16)
968 #define EXTR2(p, q) (((uint16)(~palette_animation_counter * (p)) * (q)) >> 16)
970 void DoPaletteAnimations()
972 /* Animation counter for the palette animation. */
973 static int palette_animation_counter = 0;
974 palette_animation_counter += 8;
976 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
977 const Colour *s;
978 const ExtraPaletteValues *ev = &_extra_palette_values;
979 Colour old_val[PALETTE_ANIM_SIZE];
980 const uint old_tc = palette_animation_counter;
981 uint i;
982 uint j;
984 if (blitter != NULL && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
985 palette_animation_counter = 0;
988 Colour *palette_pos = &_cur_palette.palette[PALETTE_ANIM_START]; // Points to where animations are taking place on the palette
989 /* Makes a copy of the current animation palette in old_val,
990 * so the work on the current palette could be compared, see if there has been any changes */
991 memcpy(old_val, palette_pos, sizeof(old_val));
993 /* Fizzy Drink bubbles animation */
994 s = ev->fizzy_drink;
995 j = EXTR2(512, EPV_CYCLES_FIZZY_DRINK);
996 for (i = 0; i != EPV_CYCLES_FIZZY_DRINK; i++) {
997 *palette_pos++ = s[j];
998 j++;
999 if (j == EPV_CYCLES_FIZZY_DRINK) j = 0;
1002 /* Oil refinery fire animation */
1003 s = ev->oil_refinery;
1004 j = EXTR2(512, EPV_CYCLES_OIL_REFINERY);
1005 for (i = 0; i != EPV_CYCLES_OIL_REFINERY; i++) {
1006 *palette_pos++ = s[j];
1007 j++;
1008 if (j == EPV_CYCLES_OIL_REFINERY) j = 0;
1011 /* Radio tower blinking */
1013 byte i = (palette_animation_counter >> 1) & 0x7F;
1014 byte v;
1016 if (i < 0x3f) {
1017 v = 255;
1018 } else if (i < 0x4A || i >= 0x75) {
1019 v = 128;
1020 } else {
1021 v = 20;
1023 palette_pos->r = v;
1024 palette_pos->g = 0;
1025 palette_pos->b = 0;
1026 palette_pos++;
1028 i ^= 0x40;
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++;
1042 /* Handle lighthouse and stadium animation */
1043 s = ev->lighthouse;
1044 j = EXTR(256, EPV_CYCLES_LIGHTHOUSE);
1045 for (i = 0; i != EPV_CYCLES_LIGHTHOUSE; i++) {
1046 *palette_pos++ = s[j];
1047 j++;
1048 if (j == EPV_CYCLES_LIGHTHOUSE) j = 0;
1051 /* Dark blue water */
1052 s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->dark_water_toyland : ev->dark_water;
1053 j = EXTR(320, EPV_CYCLES_DARK_WATER);
1054 for (i = 0; i != EPV_CYCLES_DARK_WATER; i++) {
1055 *palette_pos++ = s[j];
1056 j++;
1057 if (j == EPV_CYCLES_DARK_WATER) j = 0;
1060 /* Glittery water */
1061 s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->glitter_water_toyland : ev->glitter_water;
1062 j = EXTR(128, EPV_CYCLES_GLITTER_WATER);
1063 for (i = 0; i != EPV_CYCLES_GLITTER_WATER / 3; i++) {
1064 *palette_pos++ = s[j];
1065 j += 3;
1066 if (j >= EPV_CYCLES_GLITTER_WATER) j -= EPV_CYCLES_GLITTER_WATER;
1069 if (blitter != NULL && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
1070 palette_animation_counter = old_tc;
1071 } else {
1072 if (memcmp(old_val, &_cur_palette.palette[PALETTE_ANIM_START], sizeof(old_val)) != 0 && _cur_palette.count_dirty == 0) {
1073 /* Did we changed anything on the palette? Seems so. Mark it as dirty */
1074 _cur_palette.first_dirty = PALETTE_ANIM_START;
1075 _cur_palette.count_dirty = PALETTE_ANIM_SIZE;
1081 * Determine a contrasty text colour for a coloured background.
1082 * @param background Background colour.
1083 * @return TC_BLACK or TC_WHITE depending on what gives a better contrast.
1085 TextColour GetContrastColour(uint8 background)
1087 Colour c = _cur_palette.palette[background];
1088 /* Compute brightness according to http://www.w3.org/TR/AERT#color-contrast.
1089 * The following formula computes 1000 * brightness^2, with brightness being in range 0 to 255. */
1090 uint sq1000_brightness = c.r * c.r * 299 + c.g * c.g * 587 + c.b * c.b * 114;
1091 /* Compare with threshold brightness 128 (50%) */
1092 return sq1000_brightness < 128 * 128 * 1000 ? TC_WHITE : TC_BLACK;
1096 * Initialize _stringwidth_table cache
1097 * @param monospace Whether to load the monospace cache or the normal fonts.
1099 void LoadStringWidthTable(bool monospace)
1101 for (FontSize fs = monospace ? FS_MONO : FS_BEGIN; fs < (monospace ? FS_END : FS_MONO); fs++) {
1102 for (uint i = 0; i != 224; i++) {
1103 _stringwidth_table[fs][i] = GetGlyphWidth(fs, i + 32);
1107 ReInitAllWindows();
1111 * Return width of character glyph.
1112 * @param size Font of the character
1113 * @param key Character code glyph
1114 * @return Width of the character glyph
1116 byte GetCharacterWidth(FontSize size, WChar key)
1118 /* Use _stringwidth_table cache if possible */
1119 if (key >= 32 && key < 256) return _stringwidth_table[size][key - 32];
1121 return GetGlyphWidth(size, key);
1125 * Return the maximum width of single digit.
1126 * @param size Font of the digit
1127 * @return Width of the digit.
1129 byte GetDigitWidth(FontSize size)
1131 byte width = 0;
1132 for (char c = '0'; c <= '9'; c++) {
1133 width = max(GetCharacterWidth(size, c), width);
1135 return width;
1139 * Determine the broadest digits for guessing the maximum width of a n-digit number.
1140 * @param [out] front Broadest digit, which is not 0. (Use this digit as first digit for numbers with more than one digit.)
1141 * @param [out] next Broadest digit, including 0. (Use this digit for all digits, except the first one; or for numbers with only one digit.)
1142 * @param size Font of the digit
1144 void GetBroadestDigit(uint *front, uint *next, FontSize size)
1146 int width = -1;
1147 for (char c = '9'; c >= '0'; c--) {
1148 int w = GetCharacterWidth(size, c);
1149 if (w > width) {
1150 width = w;
1151 *next = c - '0';
1152 if (c != '0') *front = c - '0';
1157 void ScreenSizeChanged()
1159 _dirty_bytes_per_line = CeilDiv(_screen.width, DIRTY_BLOCK_WIDTH);
1160 _dirty_blocks = ReallocT<byte>(_dirty_blocks, _dirty_bytes_per_line * CeilDiv(_screen.height, DIRTY_BLOCK_HEIGHT));
1162 /* check the dirty rect */
1163 if (_invalid_rect.right >= _screen.width) _invalid_rect.right = _screen.width;
1164 if (_invalid_rect.bottom >= _screen.height) _invalid_rect.bottom = _screen.height;
1166 /* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */
1167 _cursor.visible = false;
1170 void UndrawMouseCursor()
1172 /* Don't undraw the mouse cursor if the screen is not ready */
1173 if (_screen.dst_ptr == NULL) return;
1175 if (_cursor.visible) {
1176 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1177 _cursor.visible = false;
1178 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);
1179 _video_driver->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
1183 void DrawMouseCursor()
1185 #if defined(WINCE)
1186 /* Don't ever draw the mouse for WinCE, as we work with a stylus */
1187 return;
1188 #endif
1190 /* Don't draw the mouse cursor if the screen is not ready */
1191 if (_screen.dst_ptr == NULL) return;
1193 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1194 int x;
1195 int y;
1196 int w;
1197 int h;
1199 /* Redraw mouse cursor but only when it's inside the window */
1200 if (!_cursor.in_window) return;
1202 /* Don't draw the mouse cursor if it's already drawn */
1203 if (_cursor.visible) {
1204 if (!_cursor.dirty) return;
1205 UndrawMouseCursor();
1208 w = _cursor.size.x;
1209 x = _cursor.pos.x + _cursor.offs.x + _cursor.short_vehicle_offset;
1210 if (x < 0) {
1211 w += x;
1212 x = 0;
1214 if (w > _screen.width - x) w = _screen.width - x;
1215 if (w <= 0) return;
1216 _cursor.draw_pos.x = x;
1217 _cursor.draw_size.x = w;
1219 h = _cursor.size.y;
1220 y = _cursor.pos.y + _cursor.offs.y;
1221 if (y < 0) {
1222 h += y;
1223 y = 0;
1225 if (h > _screen.height - y) h = _screen.height - y;
1226 if (h <= 0) return;
1227 _cursor.draw_pos.y = y;
1228 _cursor.draw_size.y = h;
1230 uint8 *buffer = _cursor_backup.Allocate(blitter->BufferSize(w, h));
1232 /* Make backup of stuff below cursor */
1233 blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, _cursor.draw_pos.x, _cursor.draw_pos.y), buffer, _cursor.draw_size.x, _cursor.draw_size.y);
1235 /* Draw cursor on screen */
1236 _cur_dpi = &_screen;
1237 DrawSprite(_cursor.sprite, _cursor.pal, _cursor.pos.x + _cursor.short_vehicle_offset, _cursor.pos.y);
1239 _video_driver->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
1241 _cursor.visible = true;
1242 _cursor.dirty = false;
1245 void RedrawScreenRect(int left, int top, int right, int bottom)
1247 assert(right <= _screen.width && bottom <= _screen.height);
1248 if (_cursor.visible) {
1249 if (right > _cursor.draw_pos.x &&
1250 left < _cursor.draw_pos.x + _cursor.draw_size.x &&
1251 bottom > _cursor.draw_pos.y &&
1252 top < _cursor.draw_pos.y + _cursor.draw_size.y) {
1253 UndrawMouseCursor();
1257 #ifdef ENABLE_NETWORK
1258 if (_networking) NetworkUndrawChatMessage();
1259 #endif /* ENABLE_NETWORK */
1261 DrawOverlappedWindowForAll(left, top, right, bottom);
1263 _video_driver->MakeDirty(left, top, right - left, bottom - top);
1267 * Repaints the rectangle blocks which are marked as 'dirty'.
1269 * @see SetDirtyBlocks
1271 void DrawDirtyBlocks()
1273 byte *b = _dirty_blocks;
1274 const int w = Align(_screen.width, DIRTY_BLOCK_WIDTH);
1275 const int h = Align(_screen.height, DIRTY_BLOCK_HEIGHT);
1276 int x;
1277 int y;
1279 if (HasModalProgress()) {
1280 /* We are generating the world, so release our rights to the map and
1281 * painting while we are waiting a bit. */
1282 _modal_progress_paint_mutex->EndCritical();
1283 _modal_progress_work_mutex->EndCritical();
1285 /* Wait a while and update _realtime_tick so we are given the rights */
1286 if (!IsFirstModalProgressLoop()) CSleep(MODAL_PROGRESS_REDRAW_TIMEOUT);
1287 _realtime_tick += MODAL_PROGRESS_REDRAW_TIMEOUT;
1288 _modal_progress_paint_mutex->BeginCritical();
1289 _modal_progress_work_mutex->BeginCritical();
1291 /* When we ended with the modal progress, do not draw the blocks.
1292 * Simply let the next run do so, otherwise we would be loading
1293 * the new state (and possibly change the blitter) when we hold
1294 * the drawing lock, which we must not do. */
1295 if (_switch_mode != SM_NONE && !HasModalProgress()) return;
1298 y = 0;
1299 do {
1300 x = 0;
1301 do {
1302 if (*b != 0) {
1303 int left;
1304 int top;
1305 int right = x + DIRTY_BLOCK_WIDTH;
1306 int bottom = y;
1307 byte *p = b;
1308 int h2;
1310 /* First try coalescing downwards */
1311 do {
1312 *p = 0;
1313 p += _dirty_bytes_per_line;
1314 bottom += DIRTY_BLOCK_HEIGHT;
1315 } while (bottom != h && *p != 0);
1317 /* Try coalescing to the right too. */
1318 h2 = (bottom - y) / DIRTY_BLOCK_HEIGHT;
1319 assert(h2 > 0);
1320 p = b;
1322 while (right != w) {
1323 byte *p2 = ++p;
1324 int h = h2;
1325 /* Check if a full line of dirty flags is set. */
1326 do {
1327 if (!*p2) goto no_more_coalesc;
1328 p2 += _dirty_bytes_per_line;
1329 } while (--h != 0);
1331 /* Wohoo, can combine it one step to the right!
1332 * Do that, and clear the bits. */
1333 right += DIRTY_BLOCK_WIDTH;
1335 h = h2;
1336 p2 = p;
1337 do {
1338 *p2 = 0;
1339 p2 += _dirty_bytes_per_line;
1340 } while (--h != 0);
1342 no_more_coalesc:
1344 left = x;
1345 top = y;
1347 if (left < _invalid_rect.left ) left = _invalid_rect.left;
1348 if (top < _invalid_rect.top ) top = _invalid_rect.top;
1349 if (right > _invalid_rect.right ) right = _invalid_rect.right;
1350 if (bottom > _invalid_rect.bottom) bottom = _invalid_rect.bottom;
1352 if (left < right && top < bottom) {
1353 RedrawScreenRect(left, top, right, bottom);
1357 } while (b++, (x += DIRTY_BLOCK_WIDTH) != w);
1358 } while (b += -(int)(w / DIRTY_BLOCK_WIDTH) + _dirty_bytes_per_line, (y += DIRTY_BLOCK_HEIGHT) != h);
1360 ++_dirty_block_colour;
1361 _invalid_rect.left = w;
1362 _invalid_rect.top = h;
1363 _invalid_rect.right = 0;
1364 _invalid_rect.bottom = 0;
1368 * This function extends the internal _invalid_rect rectangle as it
1369 * now contains the rectangle defined by the given parameters. Note
1370 * the point (0,0) is top left.
1372 * @param left The left edge of the rectangle
1373 * @param top The top edge of the rectangle
1374 * @param right The right edge of the rectangle
1375 * @param bottom The bottom edge of the rectangle
1376 * @see DrawDirtyBlocks
1378 * @todo The name of the function should be called like @c AddDirtyBlock as
1379 * it neither set a dirty rect nor add several dirty rects although
1380 * the function name is in plural. (Progman)
1382 void SetDirtyBlocks(int left, int top, int right, int bottom)
1384 byte *b;
1385 int width;
1386 int height;
1388 if (left < 0) left = 0;
1389 if (top < 0) top = 0;
1390 if (right > _screen.width) right = _screen.width;
1391 if (bottom > _screen.height) bottom = _screen.height;
1393 if (left >= right || top >= bottom) return;
1395 if (left < _invalid_rect.left ) _invalid_rect.left = left;
1396 if (top < _invalid_rect.top ) _invalid_rect.top = top;
1397 if (right > _invalid_rect.right ) _invalid_rect.right = right;
1398 if (bottom > _invalid_rect.bottom) _invalid_rect.bottom = bottom;
1400 left /= DIRTY_BLOCK_WIDTH;
1401 top /= DIRTY_BLOCK_HEIGHT;
1403 b = _dirty_blocks + top * _dirty_bytes_per_line + left;
1405 width = ((right - 1) / DIRTY_BLOCK_WIDTH) - left + 1;
1406 height = ((bottom - 1) / DIRTY_BLOCK_HEIGHT) - top + 1;
1408 assert(width > 0 && height > 0);
1410 do {
1411 int i = width;
1413 do b[--i] = 0xFF; while (i != 0);
1415 b += _dirty_bytes_per_line;
1416 } while (--height != 0);
1420 * This function mark the whole screen as dirty. This results in repainting
1421 * the whole screen. Use this with care as this function will break the
1422 * idea about marking only parts of the screen as 'dirty'.
1423 * @ingroup dirty
1425 void MarkWholeScreenDirty()
1427 SetDirtyBlocks(0, 0, _screen.width, _screen.height);
1431 * Set up a clipping area for only drawing into a certain area. To do this,
1432 * Fill a DrawPixelInfo object with the supplied relative rectangle, backup
1433 * the original (calling) _cur_dpi and assign the just returned DrawPixelInfo
1434 * _cur_dpi. When you are done, give restore _cur_dpi's original value
1435 * @param *n the DrawPixelInfo that will be the clipping rectangle box allowed
1436 * for drawing
1437 * @param left,top,width,height the relative coordinates of the clipping
1438 * rectangle relative to the current _cur_dpi. This will most likely be the
1439 * offset from the calling window coordinates
1440 * @return return false if the requested rectangle is not possible with the
1441 * current dpi pointer. Only continue of the return value is true, or you'll
1442 * get some nasty results
1444 bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
1446 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1447 const DrawPixelInfo *o = _cur_dpi;
1449 n->zoom = ZOOM_LVL_NORMAL;
1451 assert(width > 0);
1452 assert(height > 0);
1454 if ((left -= o->left) < 0) {
1455 width += left;
1456 if (width <= 0) return false;
1457 n->left = -left;
1458 left = 0;
1459 } else {
1460 n->left = 0;
1463 if (width > o->width - left) {
1464 width = o->width - left;
1465 if (width <= 0) return false;
1467 n->width = width;
1469 if ((top -= o->top) < 0) {
1470 height += top;
1471 if (height <= 0) return false;
1472 n->top = -top;
1473 top = 0;
1474 } else {
1475 n->top = 0;
1478 n->dst_ptr = blitter->MoveTo(o->dst_ptr, left, top);
1479 n->pitch = o->pitch;
1481 if (height > o->height - top) {
1482 height = o->height - top;
1483 if (height <= 0) return false;
1485 n->height = height;
1487 return true;
1491 * Update cursor dimension.
1492 * Called when changing cursor sprite resp. reloading grfs.
1494 void UpdateCursorSize()
1496 CursorVars *cv = &_cursor;
1497 const Sprite *p = GetSprite(GB(cv->sprite, 0, SPRITE_WIDTH), ST_NORMAL);
1499 cv->size.y = UnScaleByZoom(p->height, ZOOM_LVL_GUI);
1500 cv->size.x = UnScaleByZoom(p->width, ZOOM_LVL_GUI);
1501 cv->offs.x = UnScaleByZoom(p->x_offs, ZOOM_LVL_GUI);
1502 cv->offs.y = UnScaleByZoom(p->y_offs, ZOOM_LVL_GUI);
1504 cv->dirty = true;
1508 * Switch cursor to different sprite.
1509 * @param cursor Sprite to draw for the cursor.
1510 * @param pal Palette to use for recolouring.
1512 static void SetCursorSprite(CursorID cursor, PaletteID pal)
1514 CursorVars *cv = &_cursor;
1515 if (cv->sprite == cursor) return;
1517 cv->sprite = cursor;
1518 cv->pal = pal;
1519 UpdateCursorSize();
1521 cv->short_vehicle_offset = 0;
1524 static void SwitchAnimatedCursor()
1526 const AnimCursor *cur = _cursor.animate_cur;
1528 if (cur == NULL || cur->sprite == AnimCursor::LAST) cur = _cursor.animate_list;
1530 SetCursorSprite(cur->sprite, _cursor.pal);
1532 _cursor.animate_timeout = cur->display_time;
1533 _cursor.animate_cur = cur + 1;
1536 void CursorTick()
1538 if (_cursor.animate_timeout != 0 && --_cursor.animate_timeout == 0) {
1539 SwitchAnimatedCursor();
1544 * Assign a single non-animated sprite to the cursor.
1545 * @param sprite Sprite to draw for the cursor.
1546 * @param pal Palette to use for recolouring.
1547 * @see SetAnimatedMouseCursor
1549 void SetMouseCursor(CursorID sprite, PaletteID pal)
1551 /* Turn off animation */
1552 _cursor.animate_timeout = 0;
1553 /* Set cursor */
1554 SetCursorSprite(sprite, pal);
1558 * Assign an animation to the cursor.
1559 * @param table Array of animation states.
1560 * @see SetMouseCursor
1562 void SetAnimatedMouseCursor(const AnimCursor *table)
1564 _cursor.animate_list = table;
1565 _cursor.animate_cur = NULL;
1566 _cursor.pal = PAL_NONE;
1567 SwitchAnimatedCursor();
1570 bool ChangeResInGame(int width, int height)
1572 return (_screen.width == width && _screen.height == height) || _video_driver->ChangeResolution(width, height);
1575 bool ToggleFullScreen(bool fs)
1577 bool result = _video_driver->ToggleFullscreen(fs);
1578 if (_fullscreen != fs && _num_resolutions == 0) {
1579 DEBUG(driver, 0, "Could not find a suitable fullscreen resolution");
1581 return result;
1584 static int CDECL compare_res(const Dimension *pa, const Dimension *pb)
1586 int x = pa->width - pb->width;
1587 if (x != 0) return x;
1588 return pa->height - pb->height;
1591 void SortResolutions(int count)
1593 QSortT(_resolutions, count, &compare_res);