2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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 /** @file gfx.cpp Handling of drawing text and other gfx related stuff. */
11 #include "gfx_layout.h"
13 #include "zoom_func.h"
14 #include "blitter/factory.hpp"
15 #include "video/video_driver.hpp"
16 #include "strings_func.h"
17 #include "settings_type.h"
18 #include "network/network.h"
19 #include "network/network_func.h"
20 #include "window_func.h"
21 #include "newgrf_debug.h"
24 #include "table/palettes.h"
25 #include "table/string_colours.h"
26 #include "table/sprites.h"
27 #include "table/control_codes.h"
29 #include "safeguards.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 PauseMode _pause_mode
;
50 static byte _stringwidth_table
[FS_END
][224]; ///< Cache containing width of often used characters. @see GetCharacterWidth()
51 DrawPixelInfo
*_cur_dpi
;
52 byte _colour_gradient
[COLOUR_END
][8];
54 static void GfxMainBlitterViewport(const Sprite
*sprite
, int x
, int y
, BlitterMode mode
, const SubSprite
*sub
= nullptr, SpriteID sprite_id
= SPR_CURSOR_MOUSE
);
55 static void GfxMainBlitter(const Sprite
*sprite
, int x
, int y
, BlitterMode mode
, const SubSprite
*sub
= nullptr, SpriteID sprite_id
= SPR_CURSOR_MOUSE
, ZoomLevel zoom
= ZOOM_LVL_NORMAL
);
57 static ReusableBuffer
<uint8
> _cursor_backup
;
59 ZoomLevel _gui_zoom
; ///< GUI Zoom level
60 ZoomLevel _font_zoom
; ///< Font Zoom level
63 * The rect for repaint.
65 * This rectangle defines the area which should be repaint by the video driver.
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
= nullptr;
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 if (_networking
) NetworkUndrawChatMessage();
90 blitter
->ScrollBuffer(_screen
.dst_ptr
, left
, top
, width
, height
, xo
, yo
);
91 /* This part of the screen is now dirty. */
92 VideoDriver::GetInstance()->MakeDirty(left
, top
, width
, height
);
97 * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
99 * @pre dpi->zoom == ZOOM_LVL_NORMAL, right >= left, bottom >= top
100 * @param left Minimum X (inclusive)
101 * @param top Minimum Y (inclusive)
102 * @param right Maximum X (inclusive)
103 * @param bottom Maximum Y (inclusive)
104 * @param colour A 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR)
106 * FILLRECT_OPAQUE: Fill the rectangle with the specified colour
107 * FILLRECT_CHECKER: Like FILLRECT_OPAQUE, but only draw every second pixel (used to grey out things)
108 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the rectangle currently on screen
110 void GfxFillRect(int left
, int top
, int right
, int bottom
, int colour
, FillRectMode mode
)
112 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
113 const DrawPixelInfo
*dpi
= _cur_dpi
;
115 const int otop
= top
;
116 const int oleft
= left
;
118 if (dpi
->zoom
!= ZOOM_LVL_NORMAL
) return;
119 if (left
> right
|| top
> bottom
) return;
120 if (right
< dpi
->left
|| left
>= dpi
->left
+ dpi
->width
) return;
121 if (bottom
< dpi
->top
|| top
>= dpi
->top
+ dpi
->height
) return;
123 if ( (left
-= dpi
->left
) < 0) left
= 0;
124 right
= right
- dpi
->left
+ 1;
125 if (right
> dpi
->width
) right
= dpi
->width
;
129 if ( (top
-= dpi
->top
) < 0) top
= 0;
130 bottom
= bottom
- dpi
->top
+ 1;
131 if (bottom
> dpi
->height
) bottom
= dpi
->height
;
135 dst
= blitter
->MoveTo(dpi
->dst_ptr
, left
, top
);
138 default: // FILLRECT_OPAQUE
139 blitter
->DrawRect(dst
, right
, bottom
, (uint8
)colour
);
142 case FILLRECT_RECOLOUR
:
143 blitter
->DrawColourMappingRect(dst
, right
, bottom
, GB(colour
, 0, PALETTE_WIDTH
));
146 case FILLRECT_CHECKER
: {
147 byte bo
= (oleft
- left
+ dpi
->left
+ otop
- top
+ dpi
->top
) & 1;
149 for (int i
= (bo
^= 1); i
< right
; i
+= 2) blitter
->SetPixel(dst
, i
, 0, (uint8
)colour
);
150 dst
= blitter
->MoveTo(dst
, 0, 1);
151 } while (--bottom
> 0);
157 typedef std::pair
<Point
, Point
> LineSegment
;
160 * Make line segments from a polygon defined by points, translated by an offset.
161 * Entirely horizontal lines (start and end at same Y coordinate) are skipped, as they are irrelevant to scanline conversion algorithms.
162 * Generated line segments always have the lowest Y coordinate point first, i.e. original direction is lost.
163 * @param shape The polygon to convert.
164 * @param offset Offset vector subtracted from all coordinates in the shape.
165 * @return Vector of undirected line segments.
167 static std::vector
<LineSegment
> MakePolygonSegments(const std::vector
<Point
> &shape
, Point offset
)
169 std::vector
<LineSegment
> segments
;
170 if (shape
.size() < 3) return segments
; // fewer than 3 will always result in an empty polygon
171 segments
.reserve(shape
.size());
173 /* Connect first and last point by having initial previous point be the last */
174 Point prev
= shape
.back();
177 for (Point pt
: shape
) {
180 /* Create segments for all non-horizontal lines in the polygon.
181 * The segments always have lowest Y coordinate first. */
183 segments
.emplace_back(pt
, prev
);
184 } else if (prev
.y
< pt
.y
) {
185 segments
.emplace_back(prev
, pt
);
194 * Fill a polygon with colour.
195 * The odd-even winding rule is used, i.e. self-intersecting polygons will have holes in them.
196 * Left and top edges are inclusive, right and bottom edges are exclusive.
197 * @note For rectangles the GfxFillRect function will be faster.
198 * @pre dpi->zoom == ZOOM_LVL_NORMAL
199 * @param shape List of points on the polygon.
200 * @param colour An 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR).
202 * FILLRECT_OPAQUE: Fill the polygon with the specified colour.
203 * FILLRECT_CHECKER: Fill every other pixel with the specified colour, in a checkerboard pattern.
204 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the polygon.
206 void GfxFillPolygon(const std::vector
<Point
> &shape
, int colour
, FillRectMode mode
)
208 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
209 const DrawPixelInfo
*dpi
= _cur_dpi
;
210 if (dpi
->zoom
!= ZOOM_LVL_NORMAL
) return;
212 std::vector
<LineSegment
> segments
= MakePolygonSegments(shape
, Point
{ dpi
->left
, dpi
->top
});
214 /* Remove segments appearing entirely above or below the clipping area. */
215 segments
.erase(std::remove_if(segments
.begin(), segments
.end(), [dpi
](const LineSegment
&s
) { return s
.second
.y
<= 0 || s
.first
.y
>= dpi
->height
; }), segments
.end());
217 /* Check that this wasn't an empty shape (all points on a horizontal line or outside clipping.) */
218 if (segments
.empty()) return;
220 /* Sort the segments by first point Y coordinate. */
221 std::sort(segments
.begin(), segments
.end(), [](const LineSegment
&a
, const LineSegment
&b
) { return a
.first
.y
< b
.first
.y
; });
223 /* Segments intersecting current scanline. */
224 std::vector
<LineSegment
> active
;
225 /* Intersection points with a scanline.
226 * Kept outside loop to avoid repeated re-allocations. */
227 std::vector
<int> intersections
;
228 /* Normal, reasonable polygons don't have many intersections per scanline. */
230 intersections
.reserve(4);
232 /* Scan through the segments and paint each scanline. */
233 int y
= segments
.front().first
.y
;
234 std::vector
<LineSegment
>::iterator nextseg
= segments
.begin();
235 while (!active
.empty() || nextseg
!= segments
.end()) {
236 /* Clean up segments that have ended. */
237 active
.erase(std::remove_if(active
.begin(), active
.end(), [y
](const LineSegment
&s
) { return s
.second
.y
== y
; }), active
.end());
239 /* Activate all segments starting on this scanline. */
240 while (nextseg
!= segments
.end() && nextseg
->first
.y
== y
) {
241 active
.push_back(*nextseg
);
245 /* Check clipping. */
250 if (y
>= dpi
->height
) return;
252 /* Intersect scanline with all active segments. */
253 intersections
.clear();
254 for (const LineSegment
&s
: active
) {
255 const int sdx
= s
.second
.x
- s
.first
.x
;
256 const int sdy
= s
.second
.y
- s
.first
.y
;
257 const int ldy
= y
- s
.first
.y
;
258 const int x
= s
.first
.x
+ sdx
* ldy
/ sdy
;
259 intersections
.push_back(x
);
262 /* Fill between pairs of intersections. */
263 std::sort(intersections
.begin(), intersections
.end());
264 for (size_t i
= 1; i
< intersections
.size(); i
+= 2) {
265 /* Check clipping. */
266 const int x1
= max(0, intersections
[i
- 1]);
267 const int x2
= min(intersections
[i
], dpi
->width
);
268 if (x2
< 0) continue;
269 if (x1
>= dpi
->width
) continue;
271 /* Fill line y from x1 to x2. */
272 void *dst
= blitter
->MoveTo(dpi
->dst_ptr
, x1
, y
);
274 default: // FILLRECT_OPAQUE
275 blitter
->DrawRect(dst
, x2
- x1
, 1, (uint8
)colour
);
277 case FILLRECT_RECOLOUR
:
278 blitter
->DrawColourMappingRect(dst
, x2
- x1
, 1, GB(colour
, 0, PALETTE_WIDTH
));
280 case FILLRECT_CHECKER
:
281 /* Fill every other pixel, offset such that the sum of filled pixels' X and Y coordinates is odd.
282 * This creates a checkerboard effect. */
283 for (int x
= (x1
+ y
) & 1; x
< x2
- x1
; x
+= 2) {
284 blitter
->SetPixel(dst
, x
, 0, (uint8
)colour
);
296 * Check line clipping by using a linear equation and draw the visible part of
297 * the line given by x/y and x2/y2.
298 * @param video Destination pointer to draw into.
299 * @param x X coordinate of first point.
300 * @param y Y coordinate of first point.
301 * @param x2 X coordinate of second point.
302 * @param y2 Y coordinate of second point.
303 * @param screen_width With of the screen to check clipping against.
304 * @param screen_height Height of the screen to check clipping against.
305 * @param colour Colour of the line.
306 * @param width Width of the line.
307 * @param dash Length of dashes for dashed lines. 0 means solid line.
309 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)
311 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
315 if (y2
== y
|| x2
== x
) {
316 /* Special case: horizontal/vertical line. All checks already done in GfxPreprocessLine. */
317 blitter
->DrawLine(video
, x
, y
, x2
, y2
, screen_width
, screen_height
, colour
, width
, dash
);
321 int grade_y
= y2
- y
;
322 int grade_x
= x2
- x
;
324 /* Clipping rectangle. Slightly extended so we can ignore the width of the line. */
325 int extra
= (int)CeilDiv(3 * width
, 4); // not less then "width * sqrt(2) / 2"
326 Rect clip
= { -extra
, -extra
, screen_width
- 1 + extra
, screen_height
- 1 + extra
};
328 /* prevent integer overflows. */
330 while (INT_MAX
/ abs(grade_y
) < max(abs(clip
.left
- x
), abs(clip
.right
- x
))) {
333 margin
*= 2; // account for rounding errors
336 /* Imagine that the line is infinitely long and it intersects with
337 * infinitely long left and right edges of the clipping rectangle.
338 * If both intersection points are outside the clipping rectangle
339 * and both on the same side of it, we don't need to draw anything. */
340 int left_isec_y
= y
+ (clip
.left
- x
) * grade_y
/ grade_x
;
341 int right_isec_y
= y
+ (clip
.right
- x
) * grade_y
/ grade_x
;
342 if ((left_isec_y
> clip
.bottom
+ margin
&& right_isec_y
> clip
.bottom
+ margin
) ||
343 (left_isec_y
< clip
.top
- margin
&& right_isec_y
< clip
.top
- margin
)) {
347 /* It is possible to use the line equation to further reduce the amount of
348 * work the blitter has to do by shortening the effective line segment.
349 * However, in order to get that right and prevent the flickering effects
350 * of rounding errors so much additional code has to be run here that in
351 * the general case the effect is not noticeable. */
353 blitter
->DrawLine(video
, x
, y
, x2
, y2
, screen_width
, screen_height
, colour
, width
, dash
);
357 * Align parameters of a line to the given DPI and check simple clipping.
358 * @param dpi Screen parameters to align with.
359 * @param x X coordinate of first point.
360 * @param y Y coordinate of first point.
361 * @param x2 X coordinate of second point.
362 * @param y2 Y coordinate of second point.
363 * @param width Width of the line.
364 * @return True if the line is likely to be visible, false if it's certainly
367 static inline bool GfxPreprocessLine(DrawPixelInfo
*dpi
, int &x
, int &y
, int &x2
, int &y2
, int width
)
374 /* Check simple clipping */
375 if (x
+ width
/ 2 < 0 && x2
+ width
/ 2 < 0 ) return false;
376 if (y
+ width
/ 2 < 0 && y2
+ width
/ 2 < 0 ) return false;
377 if (x
- width
/ 2 > dpi
->width
&& x2
- width
/ 2 > dpi
->width
) return false;
378 if (y
- width
/ 2 > dpi
->height
&& y2
- width
/ 2 > dpi
->height
) return false;
382 void GfxDrawLine(int x
, int y
, int x2
, int y2
, int colour
, int width
, int dash
)
384 DrawPixelInfo
*dpi
= _cur_dpi
;
385 if (GfxPreprocessLine(dpi
, x
, y
, x2
, y2
, width
)) {
386 GfxDoDrawLine(dpi
->dst_ptr
, x
, y
, x2
, y2
, dpi
->width
, dpi
->height
, colour
, width
, dash
);
390 void GfxDrawLineUnscaled(int x
, int y
, int x2
, int y2
, int colour
)
392 DrawPixelInfo
*dpi
= _cur_dpi
;
393 if (GfxPreprocessLine(dpi
, x
, y
, x2
, y2
, 1)) {
394 GfxDoDrawLine(dpi
->dst_ptr
,
395 UnScaleByZoom(x
, dpi
->zoom
), UnScaleByZoom(y
, dpi
->zoom
),
396 UnScaleByZoom(x2
, dpi
->zoom
), UnScaleByZoom(y2
, dpi
->zoom
),
397 UnScaleByZoom(dpi
->width
, dpi
->zoom
), UnScaleByZoom(dpi
->height
, dpi
->zoom
), colour
, 1);
402 * Draws the projection of a parallelepiped.
403 * This can be used to draw boxes in world coordinates.
405 * @param x Screen X-coordinate of top front corner.
406 * @param y Screen Y-coordinate of top front corner.
407 * @param dx1 Screen X-length of first edge.
408 * @param dy1 Screen Y-length of first edge.
409 * @param dx2 Screen X-length of second edge.
410 * @param dy2 Screen Y-length of second edge.
411 * @param dx3 Screen X-length of third edge.
412 * @param dy3 Screen Y-length of third edge.
414 void DrawBox(int x
, int y
, int dx1
, int dy1
, int dx2
, int dy2
, int dx3
, int dy3
)
420 * <--__(dx1,dy1) /(dx2,dy2)
431 static const byte colour
= PC_WHITE
;
433 GfxDrawLineUnscaled(x
, y
, x
+ dx1
, y
+ dy1
, colour
);
434 GfxDrawLineUnscaled(x
, y
, x
+ dx2
, y
+ dy2
, colour
);
435 GfxDrawLineUnscaled(x
, y
, x
+ dx3
, y
+ dy3
, colour
);
437 GfxDrawLineUnscaled(x
+ dx1
, y
+ dy1
, x
+ dx1
+ dx2
, y
+ dy1
+ dy2
, colour
);
438 GfxDrawLineUnscaled(x
+ dx1
, y
+ dy1
, x
+ dx1
+ dx3
, y
+ dy1
+ dy3
, colour
);
439 GfxDrawLineUnscaled(x
+ dx2
, y
+ dy2
, x
+ dx2
+ dx1
, y
+ dy2
+ dy1
, colour
);
440 GfxDrawLineUnscaled(x
+ dx2
, y
+ dy2
, x
+ dx2
+ dx3
, y
+ dy2
+ dy3
, colour
);
441 GfxDrawLineUnscaled(x
+ dx3
, y
+ dy3
, x
+ dx3
+ dx1
, y
+ dy3
+ dy1
, colour
);
442 GfxDrawLineUnscaled(x
+ dx3
, y
+ dy3
, x
+ dx3
+ dx2
, y
+ dy3
+ dy2
, colour
);
446 * Set the colour remap to be for the given colour.
447 * @param colour the new colour of the remap.
449 static void SetColourRemap(TextColour colour
)
451 if (colour
== TC_INVALID
) return;
453 /* Black strings have no shading ever; the shading is black, so it
454 * would be invisible at best, but it actually makes it illegible. */
455 bool no_shade
= (colour
& TC_NO_SHADE
) != 0 || colour
== TC_BLACK
;
456 bool raw_colour
= (colour
& TC_IS_PALETTE_COLOUR
) != 0;
457 colour
&= ~(TC_NO_SHADE
| TC_IS_PALETTE_COLOUR
| TC_FORCED
);
459 _string_colourremap
[1] = raw_colour
? (byte
)colour
: _string_colourmap
[colour
];
460 _string_colourremap
[2] = no_shade
? 0 : 1;
461 _colour_remap_ptr
= _string_colourremap
;
465 * Drawing routine for drawing a laid out line of text.
466 * @param line String to draw.
467 * @param y The top most position to draw on.
468 * @param left The left most position to draw on.
469 * @param right The right most position to draw on.
470 * @param align The alignment of the string when drawing left-to-right. In the
471 * case a right-to-left language is chosen this is inverted so it
472 * will be drawn in the right direction.
473 * @param underline Whether to underline what has been drawn or not.
474 * @param truncation Whether to perform string truncation or not.
476 * @return In case of left or center alignment the right most pixel we have drawn to.
477 * In case of right alignment the left most pixel we have drawn to.
479 static int DrawLayoutLine(const ParagraphLayouter::Line
&line
, int y
, int left
, int right
, StringAlignment align
, bool underline
, bool truncation
)
481 if (line
.CountRuns() == 0) return 0;
483 int w
= line
.GetWidth();
484 int h
= line
.GetLeading();
487 * The following is needed for truncation.
488 * Depending on the text direction, we either remove bits at the rear
489 * or the front. For this we shift the entire area to draw so it fits
490 * within the left/right bounds and the side we do not truncate it on.
491 * Then we determine the truncation location, i.e. glyphs that fall
492 * outside of the range min_x - max_x will not be drawn; they are thus
493 * the truncated glyphs.
495 * At a later step we insert the dots.
498 int max_w
= right
- left
+ 1; // The maximum width.
500 int offset_x
= 0; // The offset we need for positioning the glyphs
501 int min_x
= left
; // The minimum x position to draw normal glyphs on.
502 int max_x
= right
; // The maximum x position to draw normal glyphs on.
504 truncation
&= max_w
< w
; // Whether we need to do truncation.
505 int dot_width
= 0; // Cache for the width of the dot.
506 const Sprite
*dot_sprite
= nullptr; // Cache for the sprite of the dot.
510 * Assumption may be made that all fonts of a run are of the same size.
511 * In any case, we'll use these dots for the abbreviation, so even if
512 * another size would be chosen it won't have truncated too little for
513 * the truncation dots.
515 FontCache
*fc
= ((const Font
*)line
.GetVisualRun(0).GetFont())->fc
;
516 GlyphID dot_glyph
= fc
->MapCharToGlyph('.');
517 dot_width
= fc
->GetGlyphWidth(dot_glyph
);
518 dot_sprite
= fc
->GetGlyph(dot_glyph
);
520 if (_current_text_dir
== TD_RTL
) {
521 min_x
+= 3 * dot_width
;
522 offset_x
= w
- 3 * dot_width
- max_w
;
524 max_x
-= 3 * dot_width
;
530 /* In case we have a RTL language we swap the alignment. */
531 if (!(align
& SA_FORCE
) && _current_text_dir
== TD_RTL
&& (align
& SA_HOR_MASK
) != SA_HOR_CENTER
) align
^= SA_RIGHT
;
533 /* right is the right most position to draw on. In this case we want to do
534 * calculations with the width of the string. In comparison right can be
535 * seen as lastof(todraw) and width as lengthof(todraw). They differ by 1.
536 * So most +1/-1 additions are to move from lengthof to 'indices'.
538 switch (align
& SA_HOR_MASK
) {
540 /* right + 1 = left + w */
541 right
= left
+ w
- 1;
545 left
= RoundDivSU(right
+ 1 + left
- w
, 2);
546 /* right + 1 = left + w */
547 right
= left
+ w
- 1;
551 left
= right
+ 1 - w
;
558 TextColour colour
= TC_BLACK
;
559 bool draw_shadow
= false;
560 for (int run_index
= 0; run_index
< line
.CountRuns(); run_index
++) {
561 const ParagraphLayouter::VisualRun
&run
= line
.GetVisualRun(run_index
);
562 const Font
*f
= (const Font
*)run
.GetFont();
564 FontCache
*fc
= f
->fc
;
566 SetColourRemap(colour
);
568 DrawPixelInfo
*dpi
= _cur_dpi
;
569 int dpi_left
= dpi
->left
;
570 int dpi_right
= dpi
->left
+ dpi
->width
- 1;
572 draw_shadow
= fc
->GetDrawGlyphShadow() && (colour
& TC_NO_SHADE
) == 0 && colour
!= TC_BLACK
;
574 for (int i
= 0; i
< run
.GetGlyphCount(); i
++) {
575 GlyphID glyph
= run
.GetGlyphs()[i
];
577 /* Not a valid glyph (empty) */
578 if (glyph
== 0xFFFF) continue;
580 int begin_x
= (int)run
.GetPositions()[i
* 2] + left
- offset_x
;
581 int end_x
= (int)run
.GetPositions()[i
* 2 + 2] + left
- offset_x
- 1;
582 int top
= (int)run
.GetPositions()[i
* 2 + 1] + y
;
584 /* Truncated away. */
585 if (truncation
&& (begin_x
< min_x
|| end_x
> max_x
)) continue;
587 const Sprite
*sprite
= fc
->GetGlyph(glyph
);
588 /* Check clipping (the "+ 1" is for the shadow). */
589 if (begin_x
+ sprite
->x_offs
> dpi_right
|| begin_x
+ sprite
->x_offs
+ sprite
->width
/* - 1 + 1 */ < dpi_left
) continue;
591 if (draw_shadow
&& (glyph
& SPRITE_GLYPH
) == 0) {
592 SetColourRemap(TC_BLACK
);
593 GfxMainBlitter(sprite
, begin_x
+ 1, top
+ 1, BM_COLOUR_REMAP
);
594 SetColourRemap(colour
);
596 GfxMainBlitter(sprite
, begin_x
, top
, BM_COLOUR_REMAP
);
601 int x
= (_current_text_dir
== TD_RTL
) ? left
: (right
- 3 * dot_width
);
602 for (int i
= 0; i
< 3; i
++, x
+= dot_width
) {
604 SetColourRemap(TC_BLACK
);
605 GfxMainBlitter(dot_sprite
, x
+ 1, y
+ 1, BM_COLOUR_REMAP
);
606 SetColourRemap(colour
);
608 GfxMainBlitter(dot_sprite
, x
, y
, BM_COLOUR_REMAP
);
613 GfxFillRect(left
, y
+ h
, right
, y
+ h
, _string_colourremap
[1]);
616 return (align
& SA_HOR_MASK
) == SA_RIGHT
? left
: right
;
620 * Draw string, possibly truncated to make it fit in its allocated space
622 * @param left The left most position to draw on.
623 * @param right The right most position to draw on.
624 * @param top The top most position to draw on.
625 * @param str String to draw.
626 * @param colour Colour used for drawing the string, for details see _string_colourmap in
627 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
628 * @param align The alignment of the string when drawing left-to-right. In the
629 * case a right-to-left language is chosen this is inverted so it
630 * will be drawn in the right direction.
631 * @param underline Whether to underline what has been drawn or not.
632 * @param fontsize The size of the initial characters.
633 * @return In case of left or center alignment the right most pixel we have drawn to.
634 * In case of right alignment the left most pixel we have drawn to.
636 int DrawString(int left
, int right
, int top
, const char *str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
638 /* The string may contain control chars to change the font, just use the biggest font for clipping. */
639 int max_height
= max(max(FONT_HEIGHT_SMALL
, FONT_HEIGHT_NORMAL
), max(FONT_HEIGHT_LARGE
, FONT_HEIGHT_MONO
));
641 /* Funny glyphs may extent outside the usual bounds, so relax the clipping somewhat. */
642 int extra
= max_height
/ 2;
644 if (_cur_dpi
->top
+ _cur_dpi
->height
+ extra
< top
|| _cur_dpi
->top
> top
+ max_height
+ extra
||
645 _cur_dpi
->left
+ _cur_dpi
->width
+ extra
< left
|| _cur_dpi
->left
> right
+ extra
) {
649 Layouter
layout(str
, INT32_MAX
, colour
, fontsize
);
650 if (layout
.size() == 0) return 0;
652 return DrawLayoutLine(*layout
.front(), top
, left
, right
, align
, underline
, true);
656 * Draw string, possibly truncated to make it fit in its allocated space
658 * @param left The left most position to draw on.
659 * @param right The right most position to draw on.
660 * @param top The top most position to draw on.
661 * @param str String to draw.
662 * @param colour Colour used for drawing the string, for details see _string_colourmap in
663 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
664 * @param align The alignment of the string when drawing left-to-right. In the
665 * case a right-to-left language is chosen this is inverted so it
666 * will be drawn in the right direction.
667 * @param underline Whether to underline what has been drawn or not.
668 * @param fontsize The size of the initial characters.
669 * @return In case of left or center alignment the right most pixel we have drawn to.
670 * In case of right alignment the left most pixel we have drawn to.
672 int DrawString(int left
, int right
, int top
, StringID str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
674 char buffer
[DRAW_STRING_BUFFER
];
675 GetString(buffer
, str
, lastof(buffer
));
676 return DrawString(left
, right
, top
, buffer
, colour
, align
, underline
, fontsize
);
680 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
681 * @param str string to check
682 * @param maxw maximum string width
683 * @return height of pixels of string when it is drawn
685 int GetStringHeight(const char *str
, int maxw
, FontSize fontsize
)
687 Layouter
layout(str
, maxw
, TC_FROMSTRING
, fontsize
);
688 return layout
.GetBounds().height
;
692 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
693 * @param str string to check
694 * @param maxw maximum string width
695 * @return height of pixels of string when it is drawn
697 int GetStringHeight(StringID str
, int maxw
)
699 char buffer
[DRAW_STRING_BUFFER
];
700 GetString(buffer
, str
, lastof(buffer
));
701 return GetStringHeight(buffer
, maxw
);
705 * Calculates number of lines of string. The string is changed to a multiline string if needed.
706 * @param str string to check
707 * @param maxw maximum string width
708 * @return number of lines of string when it is drawn
710 int GetStringLineCount(StringID str
, int maxw
)
712 char buffer
[DRAW_STRING_BUFFER
];
713 GetString(buffer
, str
, lastof(buffer
));
715 Layouter
layout(buffer
, maxw
);
716 return (uint
)layout
.size();
720 * Calculate string bounding box for multi-line strings.
721 * @param str String to check.
722 * @param suggestion Suggested bounding box.
723 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
725 Dimension
GetStringMultiLineBoundingBox(StringID str
, const Dimension
&suggestion
)
727 Dimension box
= {suggestion
.width
, (uint
)GetStringHeight(str
, suggestion
.width
)};
732 * Calculate string bounding box for multi-line strings.
733 * @param str String to check.
734 * @param suggestion Suggested bounding box.
735 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
737 Dimension
GetStringMultiLineBoundingBox(const char *str
, const Dimension
&suggestion
)
739 Dimension box
= {suggestion
.width
, (uint
)GetStringHeight(str
, suggestion
.width
)};
744 * Draw string, possibly over multiple lines.
746 * @param left The left most position to draw on.
747 * @param right The right most position to draw on.
748 * @param top The top most position to draw on.
749 * @param bottom The bottom most position to draw on.
750 * @param str String to draw.
751 * @param colour Colour used for drawing the string, for details see _string_colourmap in
752 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
753 * @param align The horizontal and vertical alignment of the string.
754 * @param underline Whether to underline all strings
755 * @param fontsize The size of the initial characters.
757 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
759 int DrawStringMultiLine(int left
, int right
, int top
, int bottom
, const char *str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
761 int maxw
= right
- left
+ 1;
762 int maxh
= bottom
- top
+ 1;
764 /* It makes no sense to even try if it can't be drawn anyway, or
765 * do we really want to support fonts of 0 or less pixels high? */
766 if (maxh
<= 0) return top
;
768 Layouter
layout(str
, maxw
, colour
, fontsize
);
769 int total_height
= layout
.GetBounds().height
;
771 switch (align
& SA_VERT_MASK
) {
777 y
= RoundDivSU(bottom
+ top
- total_height
, 2);
781 y
= bottom
- total_height
;
784 default: NOT_REACHED();
788 int first_line
= bottom
;
790 for (const auto &line
: layout
) {
792 int line_height
= line
->GetLeading();
793 if (y
>= top
&& y
< bottom
) {
794 last_line
= y
+ line_height
;
795 if (first_line
> y
) first_line
= y
;
797 DrawLayoutLine(*line
, y
, left
, right
, align
, underline
, false);
802 return ((align
& SA_VERT_MASK
) == SA_BOTTOM
) ? first_line
: last_line
;
806 * Draw string, possibly over multiple lines.
808 * @param left The left most position to draw on.
809 * @param right The right most position to draw on.
810 * @param top The top most position to draw on.
811 * @param bottom The bottom most position to draw on.
812 * @param str String to draw.
813 * @param colour Colour used for drawing the string, for details see _string_colourmap in
814 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
815 * @param align The horizontal and vertical alignment of the string.
816 * @param underline Whether to underline all strings
817 * @param fontsize The size of the initial characters.
819 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
821 int DrawStringMultiLine(int left
, int right
, int top
, int bottom
, StringID str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
823 char buffer
[DRAW_STRING_BUFFER
];
824 GetString(buffer
, str
, lastof(buffer
));
825 return DrawStringMultiLine(left
, right
, top
, bottom
, buffer
, colour
, align
, underline
, fontsize
);
829 * Return the string dimension in pixels. The height and width are returned
830 * in a single Dimension value. TINYFONT, BIGFONT modifiers are only
831 * supported as the first character of the string. The returned dimensions
832 * are therefore a rough estimation correct for all the current strings
833 * but not every possible combination
834 * @param str string to calculate pixel-width
835 * @param start_fontsize Fontsize to start the text with
836 * @return string width and height in pixels
838 Dimension
GetStringBoundingBox(const char *str
, FontSize start_fontsize
)
840 Layouter
layout(str
, INT32_MAX
, TC_FROMSTRING
, start_fontsize
);
841 return layout
.GetBounds();
845 * Get bounding box of a string. Uses parameters set by #SetDParam if needed.
846 * Has the same restrictions as #GetStringBoundingBox(const char *str, FontSize start_fontsize).
847 * @param strid String to examine.
848 * @return Width and height of the bounding box for the string in pixels.
850 Dimension
GetStringBoundingBox(StringID strid
)
852 char buffer
[DRAW_STRING_BUFFER
];
854 GetString(buffer
, strid
, lastof(buffer
));
855 return GetStringBoundingBox(buffer
);
859 * Get the leading corner of a character in a single-line string relative
860 * to the start of the string.
861 * @param str String containing the character.
862 * @param ch Pointer to the character in the string.
863 * @param start_fontsize Font size to start the text with.
864 * @return Upper left corner of the glyph associated with the character.
866 Point
GetCharPosInString(const char *str
, const char *ch
, FontSize start_fontsize
)
868 Layouter
layout(str
, INT32_MAX
, TC_FROMSTRING
, start_fontsize
);
869 return layout
.GetCharPosition(ch
);
873 * Get the character from a string that is drawn at a specific position.
874 * @param str String to test.
875 * @param x Position relative to the start of the string.
876 * @param start_fontsize Font size to start the text with.
877 * @return Pointer to the character at the position or nullptr if there is no character at the position.
879 const char *GetCharAtPosition(const char *str
, int x
, FontSize start_fontsize
)
881 if (x
< 0) return nullptr;
883 Layouter
layout(str
, INT32_MAX
, TC_FROMSTRING
, start_fontsize
);
884 return layout
.GetCharAtPosition(x
);
888 * Draw single character horizontally centered around (x,y)
889 * @param c Character (glyph) to draw
890 * @param x X position to draw character
891 * @param y Y position to draw character
892 * @param colour Colour to use, for details see _string_colourmap in
893 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
895 void DrawCharCentered(WChar c
, int x
, int y
, TextColour colour
)
897 SetColourRemap(colour
);
898 GfxMainBlitter(GetGlyph(FS_NORMAL
, c
), x
- GetCharacterWidth(FS_NORMAL
, c
) / 2, y
, BM_COLOUR_REMAP
);
902 * Get the size of a sprite.
903 * @param sprid Sprite to examine.
904 * @param[out] offset Optionally returns the sprite position offset.
905 * @return Sprite size in pixels.
906 * @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.
908 Dimension
GetSpriteSize(SpriteID sprid
, Point
*offset
, ZoomLevel zoom
)
910 const Sprite
*sprite
= GetSprite(sprid
, ST_NORMAL
);
912 if (offset
!= nullptr) {
913 offset
->x
= UnScaleByZoom(sprite
->x_offs
, zoom
);
914 offset
->y
= UnScaleByZoom(sprite
->y_offs
, zoom
);
918 d
.width
= max
<int>(0, UnScaleByZoom(sprite
->x_offs
+ sprite
->width
, zoom
));
919 d
.height
= max
<int>(0, UnScaleByZoom(sprite
->y_offs
+ sprite
->height
, zoom
));
924 * Helper function to get the blitter mode for different types of palettes.
925 * @param pal The palette to get the blitter mode for.
926 * @return The blitter mode associated with the palette.
928 static BlitterMode
GetBlitterMode(PaletteID pal
)
931 case PAL_NONE
: return BM_NORMAL
;
932 case PALETTE_CRASH
: return BM_CRASH_REMAP
;
933 case PALETTE_ALL_BLACK
: return BM_BLACK_REMAP
;
934 default: return BM_COLOUR_REMAP
;
939 * Draw a sprite in a viewport.
940 * @param img Image number to draw
941 * @param pal Palette to use.
942 * @param x Left coordinate of image in viewport, scaled by zoom
943 * @param y Top coordinate of image in viewport, scaled by zoom
944 * @param sub If available, draw only specified part of the sprite
946 void DrawSpriteViewport(SpriteID img
, PaletteID pal
, int x
, int y
, const SubSprite
*sub
)
948 SpriteID real_sprite
= GB(img
, 0, SPRITE_WIDTH
);
949 if (HasBit(img
, PALETTE_MODIFIER_TRANSPARENT
)) {
950 _colour_remap_ptr
= GetNonSprite(GB(pal
, 0, PALETTE_WIDTH
), ST_RECOLOUR
) + 1;
951 GfxMainBlitterViewport(GetSprite(real_sprite
, ST_NORMAL
), x
, y
, BM_TRANSPARENT
, sub
, real_sprite
);
952 } else if (pal
!= PAL_NONE
) {
953 if (HasBit(pal
, PALETTE_TEXT_RECOLOUR
)) {
954 SetColourRemap((TextColour
)GB(pal
, 0, PALETTE_WIDTH
));
956 _colour_remap_ptr
= GetNonSprite(GB(pal
, 0, PALETTE_WIDTH
), ST_RECOLOUR
) + 1;
958 GfxMainBlitterViewport(GetSprite(real_sprite
, ST_NORMAL
), x
, y
, GetBlitterMode(pal
), sub
, real_sprite
);
960 GfxMainBlitterViewport(GetSprite(real_sprite
, ST_NORMAL
), x
, y
, BM_NORMAL
, sub
, real_sprite
);
965 * Draw a sprite, not in a viewport
966 * @param img Image number to draw
967 * @param pal Palette to use.
968 * @param x Left coordinate of image in pixels
969 * @param y Top coordinate of image in pixels
970 * @param sub If available, draw only specified part of the sprite
971 * @param zoom Zoom level of sprite
973 void DrawSprite(SpriteID img
, PaletteID pal
, int x
, int y
, const SubSprite
*sub
, ZoomLevel zoom
)
975 SpriteID real_sprite
= GB(img
, 0, SPRITE_WIDTH
);
976 if (HasBit(img
, PALETTE_MODIFIER_TRANSPARENT
)) {
977 _colour_remap_ptr
= GetNonSprite(GB(pal
, 0, PALETTE_WIDTH
), ST_RECOLOUR
) + 1;
978 GfxMainBlitter(GetSprite(real_sprite
, ST_NORMAL
), x
, y
, BM_TRANSPARENT
, sub
, real_sprite
, zoom
);
979 } else if (pal
!= PAL_NONE
) {
980 if (HasBit(pal
, PALETTE_TEXT_RECOLOUR
)) {
981 SetColourRemap((TextColour
)GB(pal
, 0, PALETTE_WIDTH
));
983 _colour_remap_ptr
= GetNonSprite(GB(pal
, 0, PALETTE_WIDTH
), ST_RECOLOUR
) + 1;
985 GfxMainBlitter(GetSprite(real_sprite
, ST_NORMAL
), x
, y
, GetBlitterMode(pal
), sub
, real_sprite
, zoom
);
987 GfxMainBlitter(GetSprite(real_sprite
, ST_NORMAL
), x
, y
, BM_NORMAL
, sub
, real_sprite
, zoom
);
992 * The code for setting up the blitter mode and sprite information before finally drawing the sprite.
993 * @param sprite The sprite to draw.
994 * @param x The X location to draw.
995 * @param y The Y location to draw.
996 * @param mode The settings for the blitter to pass.
997 * @param sub Whether to only draw a sub set of the sprite.
998 * @param zoom The zoom level at which to draw the sprites.
999 * @tparam ZOOM_BASE The factor required to get the sub sprite information into the right size.
1000 * @tparam SCALED_XY Whether the X and Y are scaled or unscaled.
1002 template <int ZOOM_BASE
, bool SCALED_XY
>
1003 static void GfxBlitter(const Sprite
* const sprite
, int x
, int y
, BlitterMode mode
, const SubSprite
* const sub
, SpriteID sprite_id
, ZoomLevel zoom
)
1005 const DrawPixelInfo
*dpi
= _cur_dpi
;
1006 Blitter::BlitterParams bp
;
1010 x
= ScaleByZoom(x
, zoom
);
1011 y
= ScaleByZoom(y
, zoom
);
1014 /* Move to the correct offset */
1015 x
+= sprite
->x_offs
;
1016 y
+= sprite
->y_offs
;
1018 if (sub
== nullptr) {
1022 bp
.width
= UnScaleByZoom(sprite
->width
, zoom
);
1023 bp
.height
= UnScaleByZoom(sprite
->height
, zoom
);
1025 /* Amount of pixels to clip from the source sprite */
1026 int clip_left
= max(0, -sprite
->x_offs
+ sub
->left
* ZOOM_BASE
);
1027 int clip_top
= max(0, -sprite
->y_offs
+ sub
->top
* ZOOM_BASE
);
1028 int clip_right
= max(0, sprite
->width
- (-sprite
->x_offs
+ (sub
->right
+ 1) * ZOOM_BASE
));
1029 int clip_bottom
= max(0, sprite
->height
- (-sprite
->y_offs
+ (sub
->bottom
+ 1) * ZOOM_BASE
));
1031 if (clip_left
+ clip_right
>= sprite
->width
) return;
1032 if (clip_top
+ clip_bottom
>= sprite
->height
) return;
1034 bp
.skip_left
= UnScaleByZoomLower(clip_left
, zoom
);
1035 bp
.skip_top
= UnScaleByZoomLower(clip_top
, zoom
);
1036 bp
.width
= UnScaleByZoom(sprite
->width
- clip_left
- clip_right
, zoom
);
1037 bp
.height
= UnScaleByZoom(sprite
->height
- clip_top
- clip_bottom
, zoom
);
1039 x
+= ScaleByZoom(bp
.skip_left
, zoom
);
1040 y
+= ScaleByZoom(bp
.skip_top
, zoom
);
1043 /* Copy the main data directly from the sprite */
1044 bp
.sprite
= sprite
->data
;
1045 bp
.sprite_width
= sprite
->width
;
1046 bp
.sprite_height
= sprite
->height
;
1050 bp
.dst
= dpi
->dst_ptr
;
1051 bp
.pitch
= dpi
->pitch
;
1052 bp
.remap
= _colour_remap_ptr
;
1054 assert(sprite
->width
> 0);
1055 assert(sprite
->height
> 0);
1057 if (bp
.width
<= 0) return;
1058 if (bp
.height
<= 0) return;
1060 y
-= SCALED_XY
? ScaleByZoom(dpi
->top
, zoom
) : dpi
->top
;
1061 int y_unscaled
= UnScaleByZoom(y
, zoom
);
1062 /* Check for top overflow */
1064 bp
.height
-= -y_unscaled
;
1065 if (bp
.height
<= 0) return;
1066 bp
.skip_top
+= -y_unscaled
;
1069 bp
.top
= y_unscaled
;
1072 /* Check for bottom overflow */
1073 y
+= SCALED_XY
? ScaleByZoom(bp
.height
- dpi
->height
, zoom
) : ScaleByZoom(bp
.height
, zoom
) - dpi
->height
;
1075 bp
.height
-= UnScaleByZoom(y
, zoom
);
1076 if (bp
.height
<= 0) return;
1079 x
-= SCALED_XY
? ScaleByZoom(dpi
->left
, zoom
) : dpi
->left
;
1080 int x_unscaled
= UnScaleByZoom(x
, zoom
);
1081 /* Check for left overflow */
1083 bp
.width
-= -x_unscaled
;
1084 if (bp
.width
<= 0) return;
1085 bp
.skip_left
+= -x_unscaled
;
1088 bp
.left
= x_unscaled
;
1091 /* Check for right overflow */
1092 x
+= SCALED_XY
? ScaleByZoom(bp
.width
- dpi
->width
, zoom
) : ScaleByZoom(bp
.width
, zoom
) - dpi
->width
;
1094 bp
.width
-= UnScaleByZoom(x
, zoom
);
1095 if (bp
.width
<= 0) return;
1098 assert(bp
.skip_left
+ bp
.width
<= UnScaleByZoom(sprite
->width
, zoom
));
1099 assert(bp
.skip_top
+ bp
.height
<= UnScaleByZoom(sprite
->height
, zoom
));
1101 /* We do not want to catch the mouse. However we also use that spritenumber for unknown (text) sprites. */
1102 if (_newgrf_debug_sprite_picker
.mode
== SPM_REDRAW
&& sprite_id
!= SPR_CURSOR_MOUSE
) {
1103 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1104 void *topleft
= blitter
->MoveTo(bp
.dst
, bp
.left
, bp
.top
);
1105 void *bottomright
= blitter
->MoveTo(topleft
, bp
.width
- 1, bp
.height
- 1);
1107 void *clicked
= _newgrf_debug_sprite_picker
.clicked_pixel
;
1109 if (topleft
<= clicked
&& clicked
<= bottomright
) {
1110 uint offset
= (((size_t)clicked
- (size_t)topleft
) / (blitter
->GetScreenDepth() / 8)) % bp
.pitch
;
1111 if (offset
< (uint
)bp
.width
) {
1112 include(_newgrf_debug_sprite_picker
.sprites
, sprite_id
);
1117 BlitterFactory::GetCurrentBlitter()->Draw(&bp
, mode
, zoom
);
1120 static void GfxMainBlitterViewport(const Sprite
*sprite
, int x
, int y
, BlitterMode mode
, const SubSprite
*sub
, SpriteID sprite_id
)
1122 GfxBlitter
<ZOOM_LVL_BASE
, false>(sprite
, x
, y
, mode
, sub
, sprite_id
, _cur_dpi
->zoom
);
1125 static void GfxMainBlitter(const Sprite
*sprite
, int x
, int y
, BlitterMode mode
, const SubSprite
*sub
, SpriteID sprite_id
, ZoomLevel zoom
)
1127 GfxBlitter
<1, true>(sprite
, x
, y
, mode
, sub
, sprite_id
, zoom
);
1130 void DoPaletteAnimations();
1132 void GfxInitPalettes()
1134 memcpy(&_cur_palette
, &_palette
, sizeof(_cur_palette
));
1135 DoPaletteAnimations();
1138 #define EXTR(p, q) (((uint16)(palette_animation_counter * (p)) * (q)) >> 16)
1139 #define EXTR2(p, q) (((uint16)(~palette_animation_counter * (p)) * (q)) >> 16)
1141 void DoPaletteAnimations()
1143 /* Animation counter for the palette animation. */
1144 static int palette_animation_counter
= 0;
1145 palette_animation_counter
+= 8;
1147 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1149 const ExtraPaletteValues
*ev
= &_extra_palette_values
;
1150 Colour old_val
[PALETTE_ANIM_SIZE
];
1151 const uint old_tc
= palette_animation_counter
;
1155 if (blitter
!= nullptr && blitter
->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE
) {
1156 palette_animation_counter
= 0;
1159 Colour
*palette_pos
= &_cur_palette
.palette
[PALETTE_ANIM_START
]; // Points to where animations are taking place on the palette
1160 /* Makes a copy of the current animation palette in old_val,
1161 * so the work on the current palette could be compared, see if there has been any changes */
1162 memcpy(old_val
, palette_pos
, sizeof(old_val
));
1164 /* Fizzy Drink bubbles animation */
1165 s
= ev
->fizzy_drink
;
1166 j
= EXTR2(512, EPV_CYCLES_FIZZY_DRINK
);
1167 for (i
= 0; i
!= EPV_CYCLES_FIZZY_DRINK
; i
++) {
1168 *palette_pos
++ = s
[j
];
1170 if (j
== EPV_CYCLES_FIZZY_DRINK
) j
= 0;
1173 /* Oil refinery fire animation */
1174 s
= ev
->oil_refinery
;
1175 j
= EXTR2(512, EPV_CYCLES_OIL_REFINERY
);
1176 for (i
= 0; i
!= EPV_CYCLES_OIL_REFINERY
; i
++) {
1177 *palette_pos
++ = s
[j
];
1179 if (j
== EPV_CYCLES_OIL_REFINERY
) j
= 0;
1182 /* Radio tower blinking */
1184 byte i
= (palette_animation_counter
>> 1) & 0x7F;
1189 } else if (i
< 0x4A || i
>= 0x75) {
1202 } else if (i
< 0x4A || i
>= 0x75) {
1213 /* Handle lighthouse and stadium animation */
1215 j
= EXTR(256, EPV_CYCLES_LIGHTHOUSE
);
1216 for (i
= 0; i
!= EPV_CYCLES_LIGHTHOUSE
; i
++) {
1217 *palette_pos
++ = s
[j
];
1219 if (j
== EPV_CYCLES_LIGHTHOUSE
) j
= 0;
1222 /* Dark blue water */
1223 s
= (_settings_game
.game_creation
.landscape
== LT_TOYLAND
) ? ev
->dark_water_toyland
: ev
->dark_water
;
1224 j
= EXTR(320, EPV_CYCLES_DARK_WATER
);
1225 for (i
= 0; i
!= EPV_CYCLES_DARK_WATER
; i
++) {
1226 *palette_pos
++ = s
[j
];
1228 if (j
== EPV_CYCLES_DARK_WATER
) j
= 0;
1231 /* Glittery water */
1232 s
= (_settings_game
.game_creation
.landscape
== LT_TOYLAND
) ? ev
->glitter_water_toyland
: ev
->glitter_water
;
1233 j
= EXTR(128, EPV_CYCLES_GLITTER_WATER
);
1234 for (i
= 0; i
!= EPV_CYCLES_GLITTER_WATER
/ 3; i
++) {
1235 *palette_pos
++ = s
[j
];
1237 if (j
>= EPV_CYCLES_GLITTER_WATER
) j
-= EPV_CYCLES_GLITTER_WATER
;
1240 if (blitter
!= nullptr && blitter
->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE
) {
1241 palette_animation_counter
= old_tc
;
1243 if (memcmp(old_val
, &_cur_palette
.palette
[PALETTE_ANIM_START
], sizeof(old_val
)) != 0 && _cur_palette
.count_dirty
== 0) {
1244 /* Did we changed anything on the palette? Seems so. Mark it as dirty */
1245 _cur_palette
.first_dirty
= PALETTE_ANIM_START
;
1246 _cur_palette
.count_dirty
= PALETTE_ANIM_SIZE
;
1252 * Determine a contrasty text colour for a coloured background.
1253 * @param background Background colour.
1254 * @param threshold Background colour brightness threshold below which the background is considered dark and TC_WHITE is returned, range: 0 - 255, default 128.
1255 * @return TC_BLACK or TC_WHITE depending on what gives a better contrast.
1257 TextColour
GetContrastColour(uint8 background
, uint8 threshold
)
1259 Colour c
= _cur_palette
.palette
[background
];
1260 /* Compute brightness according to http://www.w3.org/TR/AERT#color-contrast.
1261 * The following formula computes 1000 * brightness^2, with brightness being in range 0 to 255. */
1262 uint sq1000_brightness
= c
.r
* c
.r
* 299 + c
.g
* c
.g
* 587 + c
.b
* c
.b
* 114;
1263 /* Compare with threshold brightness which defaults to 128 (50%) */
1264 return sq1000_brightness
< ((uint
) threshold
) * ((uint
) threshold
) * 1000 ? TC_WHITE
: TC_BLACK
;
1268 * Initialize _stringwidth_table cache
1269 * @param monospace Whether to load the monospace cache or the normal fonts.
1271 void LoadStringWidthTable(bool monospace
)
1275 for (FontSize fs
= monospace
? FS_MONO
: FS_BEGIN
; fs
< (monospace
? FS_END
: FS_MONO
); fs
++) {
1276 for (uint i
= 0; i
!= 224; i
++) {
1277 _stringwidth_table
[fs
][i
] = GetGlyphWidth(fs
, i
+ 32);
1285 * Return width of character glyph.
1286 * @param size Font of the character
1287 * @param key Character code glyph
1288 * @return Width of the character glyph
1290 byte
GetCharacterWidth(FontSize size
, WChar key
)
1292 /* Use _stringwidth_table cache if possible */
1293 if (key
>= 32 && key
< 256) return _stringwidth_table
[size
][key
- 32];
1295 return GetGlyphWidth(size
, key
);
1299 * Return the maximum width of single digit.
1300 * @param size Font of the digit
1301 * @return Width of the digit.
1303 byte
GetDigitWidth(FontSize size
)
1306 for (char c
= '0'; c
<= '9'; c
++) {
1307 width
= max(GetCharacterWidth(size
, c
), width
);
1313 * Determine the broadest digits for guessing the maximum width of a n-digit number.
1314 * @param[out] front Broadest digit, which is not 0. (Use this digit as first digit for numbers with more than one digit.)
1315 * @param[out] next Broadest digit, including 0. (Use this digit for all digits, except the first one; or for numbers with only one digit.)
1316 * @param size Font of the digit
1318 void GetBroadestDigit(uint
*front
, uint
*next
, FontSize size
)
1321 for (char c
= '9'; c
>= '0'; c
--) {
1322 int w
= GetCharacterWidth(size
, c
);
1326 if (c
!= '0') *front
= c
- '0';
1331 void ScreenSizeChanged()
1333 _dirty_bytes_per_line
= CeilDiv(_screen
.width
, DIRTY_BLOCK_WIDTH
);
1334 _dirty_blocks
= ReallocT
<byte
>(_dirty_blocks
, _dirty_bytes_per_line
* CeilDiv(_screen
.height
, DIRTY_BLOCK_HEIGHT
));
1336 /* check the dirty rect */
1337 if (_invalid_rect
.right
>= _screen
.width
) _invalid_rect
.right
= _screen
.width
;
1338 if (_invalid_rect
.bottom
>= _screen
.height
) _invalid_rect
.bottom
= _screen
.height
;
1340 /* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */
1341 _cursor
.visible
= false;
1344 void UndrawMouseCursor()
1346 /* Don't undraw the mouse cursor if the screen is not ready */
1347 if (_screen
.dst_ptr
== nullptr) return;
1349 if (_cursor
.visible
) {
1350 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1351 _cursor
.visible
= false;
1352 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
);
1353 VideoDriver::GetInstance()->MakeDirty(_cursor
.draw_pos
.x
, _cursor
.draw_pos
.y
, _cursor
.draw_size
.x
, _cursor
.draw_size
.y
);
1357 void DrawMouseCursor()
1359 /* Don't draw the mouse cursor if the screen is not ready */
1360 if (_screen
.dst_ptr
== nullptr) return;
1362 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1364 /* Redraw mouse cursor but only when it's inside the window */
1365 if (!_cursor
.in_window
) return;
1367 /* Don't draw the mouse cursor if it's already drawn */
1368 if (_cursor
.visible
) {
1369 if (!_cursor
.dirty
) return;
1370 UndrawMouseCursor();
1373 /* Determine visible area */
1374 int left
= _cursor
.pos
.x
+ _cursor
.total_offs
.x
;
1375 int width
= _cursor
.total_size
.x
;
1380 if (left
+ width
> _screen
.width
) {
1381 width
= _screen
.width
- left
;
1383 if (width
<= 0) return;
1385 int top
= _cursor
.pos
.y
+ _cursor
.total_offs
.y
;
1386 int height
= _cursor
.total_size
.y
;
1391 if (top
+ height
> _screen
.height
) {
1392 height
= _screen
.height
- top
;
1394 if (height
<= 0) return;
1396 _cursor
.draw_pos
.x
= left
;
1397 _cursor
.draw_pos
.y
= top
;
1398 _cursor
.draw_size
.x
= width
;
1399 _cursor
.draw_size
.y
= height
;
1401 uint8
*buffer
= _cursor_backup
.Allocate(blitter
->BufferSize(_cursor
.draw_size
.x
, _cursor
.draw_size
.y
));
1403 /* Make backup of stuff below cursor */
1404 blitter
->CopyToBuffer(blitter
->MoveTo(_screen
.dst_ptr
, _cursor
.draw_pos
.x
, _cursor
.draw_pos
.y
), buffer
, _cursor
.draw_size
.x
, _cursor
.draw_size
.y
);
1406 /* Draw cursor on screen */
1407 _cur_dpi
= &_screen
;
1408 for (uint i
= 0; i
< _cursor
.sprite_count
; ++i
) {
1409 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
);
1412 VideoDriver::GetInstance()->MakeDirty(_cursor
.draw_pos
.x
, _cursor
.draw_pos
.y
, _cursor
.draw_size
.x
, _cursor
.draw_size
.y
);
1414 _cursor
.visible
= true;
1415 _cursor
.dirty
= false;
1418 void RedrawScreenRect(int left
, int top
, int right
, int bottom
)
1420 assert(right
<= _screen
.width
&& bottom
<= _screen
.height
);
1421 if (_cursor
.visible
) {
1422 if (right
> _cursor
.draw_pos
.x
&&
1423 left
< _cursor
.draw_pos
.x
+ _cursor
.draw_size
.x
&&
1424 bottom
> _cursor
.draw_pos
.y
&&
1425 top
< _cursor
.draw_pos
.y
+ _cursor
.draw_size
.y
) {
1426 UndrawMouseCursor();
1430 if (_networking
) NetworkUndrawChatMessage();
1432 DrawOverlappedWindowForAll(left
, top
, right
, bottom
);
1434 VideoDriver::GetInstance()->MakeDirty(left
, top
, right
- left
, bottom
- top
);
1438 * Repaints the rectangle blocks which are marked as 'dirty'.
1440 * @see SetDirtyBlocks
1442 void DrawDirtyBlocks()
1444 byte
*b
= _dirty_blocks
;
1445 const int w
= Align(_screen
.width
, DIRTY_BLOCK_WIDTH
);
1446 const int h
= Align(_screen
.height
, DIRTY_BLOCK_HEIGHT
);
1450 if (HasModalProgress()) {
1451 /* We are generating the world, so release our rights to the map and
1452 * painting while we are waiting a bit. */
1453 _modal_progress_paint_mutex
.unlock();
1454 _modal_progress_work_mutex
.unlock();
1456 /* Wait a while and update _realtime_tick so we are given the rights */
1457 if (!IsFirstModalProgressLoop()) CSleep(MODAL_PROGRESS_REDRAW_TIMEOUT
);
1458 _realtime_tick
+= MODAL_PROGRESS_REDRAW_TIMEOUT
;
1460 /* Modal progress thread may need blitter access while we are waiting for it. */
1461 VideoDriver::GetInstance()->ReleaseBlitterLock();
1462 _modal_progress_paint_mutex
.lock();
1463 VideoDriver::GetInstance()->AcquireBlitterLock();
1464 _modal_progress_work_mutex
.lock();
1466 /* When we ended with the modal progress, do not draw the blocks.
1467 * Simply let the next run do so, otherwise we would be loading
1468 * the new state (and possibly change the blitter) when we hold
1469 * the drawing lock, which we must not do. */
1470 if (_switch_mode
!= SM_NONE
&& !HasModalProgress()) return;
1480 int right
= x
+ DIRTY_BLOCK_WIDTH
;
1485 /* First try coalescing downwards */
1488 p
+= _dirty_bytes_per_line
;
1489 bottom
+= DIRTY_BLOCK_HEIGHT
;
1490 } while (bottom
!= h
&& *p
!= 0);
1492 /* Try coalescing to the right too. */
1493 h2
= (bottom
- y
) / DIRTY_BLOCK_HEIGHT
;
1497 while (right
!= w
) {
1500 /* Check if a full line of dirty flags is set. */
1502 if (!*p2
) goto no_more_coalesc
;
1503 p2
+= _dirty_bytes_per_line
;
1506 /* Wohoo, can combine it one step to the right!
1507 * Do that, and clear the bits. */
1508 right
+= DIRTY_BLOCK_WIDTH
;
1514 p2
+= _dirty_bytes_per_line
;
1522 if (left
< _invalid_rect
.left
) left
= _invalid_rect
.left
;
1523 if (top
< _invalid_rect
.top
) top
= _invalid_rect
.top
;
1524 if (right
> _invalid_rect
.right
) right
= _invalid_rect
.right
;
1525 if (bottom
> _invalid_rect
.bottom
) bottom
= _invalid_rect
.bottom
;
1527 if (left
< right
&& top
< bottom
) {
1528 RedrawScreenRect(left
, top
, right
, bottom
);
1532 } while (b
++, (x
+= DIRTY_BLOCK_WIDTH
) != w
);
1533 } while (b
+= -(int)(w
/ DIRTY_BLOCK_WIDTH
) + _dirty_bytes_per_line
, (y
+= DIRTY_BLOCK_HEIGHT
) != h
);
1535 ++_dirty_block_colour
;
1536 _invalid_rect
.left
= w
;
1537 _invalid_rect
.top
= h
;
1538 _invalid_rect
.right
= 0;
1539 _invalid_rect
.bottom
= 0;
1543 * This function extends the internal _invalid_rect rectangle as it
1544 * now contains the rectangle defined by the given parameters. Note
1545 * the point (0,0) is top left.
1547 * @param left The left edge of the rectangle
1548 * @param top The top edge of the rectangle
1549 * @param right The right edge of the rectangle
1550 * @param bottom The bottom edge of the rectangle
1551 * @see DrawDirtyBlocks
1553 * @todo The name of the function should be called like @c AddDirtyBlock as
1554 * it neither set a dirty rect nor add several dirty rects although
1555 * the function name is in plural. (Progman)
1557 void SetDirtyBlocks(int left
, int top
, int right
, int bottom
)
1563 if (left
< 0) left
= 0;
1564 if (top
< 0) top
= 0;
1565 if (right
> _screen
.width
) right
= _screen
.width
;
1566 if (bottom
> _screen
.height
) bottom
= _screen
.height
;
1568 if (left
>= right
|| top
>= bottom
) return;
1570 if (left
< _invalid_rect
.left
) _invalid_rect
.left
= left
;
1571 if (top
< _invalid_rect
.top
) _invalid_rect
.top
= top
;
1572 if (right
> _invalid_rect
.right
) _invalid_rect
.right
= right
;
1573 if (bottom
> _invalid_rect
.bottom
) _invalid_rect
.bottom
= bottom
;
1575 left
/= DIRTY_BLOCK_WIDTH
;
1576 top
/= DIRTY_BLOCK_HEIGHT
;
1578 b
= _dirty_blocks
+ top
* _dirty_bytes_per_line
+ left
;
1580 width
= ((right
- 1) / DIRTY_BLOCK_WIDTH
) - left
+ 1;
1581 height
= ((bottom
- 1) / DIRTY_BLOCK_HEIGHT
) - top
+ 1;
1583 assert(width
> 0 && height
> 0);
1588 do b
[--i
] = 0xFF; while (i
!= 0);
1590 b
+= _dirty_bytes_per_line
;
1591 } while (--height
!= 0);
1595 * This function mark the whole screen as dirty. This results in repainting
1596 * the whole screen. Use this with care as this function will break the
1597 * idea about marking only parts of the screen as 'dirty'.
1600 void MarkWholeScreenDirty()
1602 SetDirtyBlocks(0, 0, _screen
.width
, _screen
.height
);
1606 * Set up a clipping area for only drawing into a certain area. To do this,
1607 * Fill a DrawPixelInfo object with the supplied relative rectangle, backup
1608 * the original (calling) _cur_dpi and assign the just returned DrawPixelInfo
1609 * _cur_dpi. When you are done, give restore _cur_dpi's original value
1610 * @param *n the DrawPixelInfo that will be the clipping rectangle box allowed
1612 * @param left,top,width,height the relative coordinates of the clipping
1613 * rectangle relative to the current _cur_dpi. This will most likely be the
1614 * offset from the calling window coordinates
1615 * @return return false if the requested rectangle is not possible with the
1616 * current dpi pointer. Only continue of the return value is true, or you'll
1617 * get some nasty results
1619 bool FillDrawPixelInfo(DrawPixelInfo
*n
, int left
, int top
, int width
, int height
)
1621 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1622 const DrawPixelInfo
*o
= _cur_dpi
;
1624 n
->zoom
= ZOOM_LVL_NORMAL
;
1629 if ((left
-= o
->left
) < 0) {
1631 if (width
<= 0) return false;
1638 if (width
> o
->width
- left
) {
1639 width
= o
->width
- left
;
1640 if (width
<= 0) return false;
1644 if ((top
-= o
->top
) < 0) {
1646 if (height
<= 0) return false;
1653 n
->dst_ptr
= blitter
->MoveTo(o
->dst_ptr
, left
, top
);
1654 n
->pitch
= o
->pitch
;
1656 if (height
> o
->height
- top
) {
1657 height
= o
->height
- top
;
1658 if (height
<= 0) return false;
1666 * Update cursor dimension.
1667 * Called when changing cursor sprite resp. reloading grfs.
1669 void UpdateCursorSize()
1671 /* Ignore setting any cursor before the sprites are loaded. */
1672 if (GetMaxSpriteID() == 0) return;
1674 assert_compile(lengthof(_cursor
.sprite_seq
) == lengthof(_cursor
.sprite_pos
));
1675 assert(_cursor
.sprite_count
<= lengthof(_cursor
.sprite_seq
));
1676 for (uint i
= 0; i
< _cursor
.sprite_count
; ++i
) {
1677 const Sprite
*p
= GetSprite(GB(_cursor
.sprite_seq
[i
].sprite
, 0, SPRITE_WIDTH
), ST_NORMAL
);
1679 offs
.x
= UnScaleGUI(p
->x_offs
) + _cursor
.sprite_pos
[i
].x
;
1680 offs
.y
= UnScaleGUI(p
->y_offs
) + _cursor
.sprite_pos
[i
].y
;
1681 size
.x
= UnScaleGUI(p
->width
);
1682 size
.y
= UnScaleGUI(p
->height
);
1685 _cursor
.total_offs
= offs
;
1686 _cursor
.total_size
= size
;
1688 int right
= max(_cursor
.total_offs
.x
+ _cursor
.total_size
.x
, offs
.x
+ size
.x
);
1689 int bottom
= max(_cursor
.total_offs
.y
+ _cursor
.total_size
.y
, offs
.y
+ size
.y
);
1690 if (offs
.x
< _cursor
.total_offs
.x
) _cursor
.total_offs
.x
= offs
.x
;
1691 if (offs
.y
< _cursor
.total_offs
.y
) _cursor
.total_offs
.y
= offs
.y
;
1692 _cursor
.total_size
.x
= right
- _cursor
.total_offs
.x
;
1693 _cursor
.total_size
.y
= bottom
- _cursor
.total_offs
.y
;
1697 _cursor
.dirty
= true;
1701 * Switch cursor to different sprite.
1702 * @param cursor Sprite to draw for the cursor.
1703 * @param pal Palette to use for recolouring.
1705 static void SetCursorSprite(CursorID cursor
, PaletteID pal
)
1707 if (_cursor
.sprite_count
== 1 && _cursor
.sprite_seq
[0].sprite
== cursor
&& _cursor
.sprite_seq
[0].pal
== pal
) return;
1709 _cursor
.sprite_count
= 1;
1710 _cursor
.sprite_seq
[0].sprite
= cursor
;
1711 _cursor
.sprite_seq
[0].pal
= pal
;
1712 _cursor
.sprite_pos
[0].x
= 0;
1713 _cursor
.sprite_pos
[0].y
= 0;
1718 static void SwitchAnimatedCursor()
1720 const AnimCursor
*cur
= _cursor
.animate_cur
;
1722 if (cur
== nullptr || cur
->sprite
== AnimCursor::LAST
) cur
= _cursor
.animate_list
;
1724 SetCursorSprite(cur
->sprite
, _cursor
.sprite_seq
[0].pal
);
1726 _cursor
.animate_timeout
= cur
->display_time
;
1727 _cursor
.animate_cur
= cur
+ 1;
1732 if (_cursor
.animate_timeout
!= 0 && --_cursor
.animate_timeout
== 0) {
1733 SwitchAnimatedCursor();
1738 * Set or unset the ZZZ cursor.
1739 * @param busy Whether to show the ZZZ cursor.
1741 void SetMouseCursorBusy(bool busy
)
1744 if (_cursor
.sprite_seq
[0].sprite
== SPR_CURSOR_MOUSE
) SetMouseCursor(SPR_CURSOR_ZZZ
, PAL_NONE
);
1746 if (_cursor
.sprite_seq
[0].sprite
== SPR_CURSOR_ZZZ
) SetMouseCursor(SPR_CURSOR_MOUSE
, PAL_NONE
);
1751 * Assign a single non-animated sprite to the cursor.
1752 * @param sprite Sprite to draw for the cursor.
1753 * @param pal Palette to use for recolouring.
1754 * @see SetAnimatedMouseCursor
1756 void SetMouseCursor(CursorID sprite
, PaletteID pal
)
1758 /* Turn off animation */
1759 _cursor
.animate_timeout
= 0;
1761 SetCursorSprite(sprite
, pal
);
1765 * Assign an animation to the cursor.
1766 * @param table Array of animation states.
1767 * @see SetMouseCursor
1769 void SetAnimatedMouseCursor(const AnimCursor
*table
)
1771 _cursor
.animate_list
= table
;
1772 _cursor
.animate_cur
= nullptr;
1773 _cursor
.sprite_seq
[0].pal
= PAL_NONE
;
1774 SwitchAnimatedCursor();
1778 * Update cursor position on mouse movement.
1779 * @param x New X position.
1780 * @param y New Y position.
1781 * @param queued_warp True, if the OS queues mouse warps after pending mouse movement events.
1782 * False, if the warp applies instantaneous.
1783 * @return true, if the OS cursor position should be warped back to this->pos.
1785 bool CursorVars::UpdateCursorPosition(int x
, int y
, bool queued_warp
)
1787 /* Detecting relative mouse movement is somewhat tricky.
1788 * - There may be multiple mouse move events in the video driver queue (esp. when OpenTTD lags a bit).
1789 * - When we request warping the mouse position (return true), a mouse move event is appended at the end of the queue.
1791 * So, when this->fix_at is active, we use the following strategy:
1792 * - The first movement triggers the warp to reset the mouse position.
1793 * - Subsequent events have to compute movement relative to the previous event.
1794 * - The relative movement is finished, when we receive the event matching the warp.
1797 if (x
== this->pos
.x
&& y
== this->pos
.y
) {
1798 /* Warp finished. */
1799 this->queued_warp
= false;
1802 this->delta
.x
= x
- (this->queued_warp
? this->last_position
.x
: this->pos
.x
);
1803 this->delta
.y
= y
- (this->queued_warp
? this->last_position
.y
: this->pos
.y
);
1805 this->last_position
.x
= x
;
1806 this->last_position
.y
= y
;
1808 bool need_warp
= false;
1810 if (this->delta
.x
!= 0 || this->delta
.y
!= 0) {
1812 * Note: We also trigger warping again, if there is already a pending warp.
1813 * This makes it more tolerant about the OS or other software in between
1814 * botchering the warp. */
1815 this->queued_warp
= queued_warp
;
1818 } else if (this->pos
.x
!= x
|| this->pos
.y
!= y
) {
1819 this->queued_warp
= false; // Cancel warping, we are no longer confining the position.
1827 bool ChangeResInGame(int width
, int height
)
1829 return (_screen
.width
== width
&& _screen
.height
== height
) || VideoDriver::GetInstance()->ChangeResolution(width
, height
);
1832 bool ToggleFullScreen(bool fs
)
1834 bool result
= VideoDriver::GetInstance()->ToggleFullscreen(fs
);
1835 if (_fullscreen
!= fs
&& _resolutions
.empty()) {
1836 DEBUG(driver
, 0, "Could not find a suitable fullscreen resolution");
1841 void SortResolutions()
1843 std::sort(_resolutions
.begin(), _resolutions
.end());