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