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