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?
37 uint16 _game_speed
= 100; ///< Current game-speed; 100 is 1x, 0 is infinite.
38 bool _left_button_down
; ///< Is left mouse button pressed?
39 bool _left_button_clicked
; ///< Is left mouse button clicked?
40 bool _right_button_down
; ///< Is right mouse button pressed?
41 bool _right_button_clicked
; ///< Is right mouse button clicked?
42 DrawPixelInfo _screen
;
43 bool _screen_disable_anim
= false; ///< Disable palette animation (important for 32bpp-anim blitter during giant screenshot)
44 std::atomic
<bool> _exit_game
;
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 std::recursive_mutex _palette_mutex
; ///< To coordinate access to _cur_palette.
56 static void GfxMainBlitterViewport(const Sprite
*sprite
, int x
, int y
, BlitterMode mode
, const SubSprite
*sub
= nullptr, SpriteID sprite_id
= SPR_CURSOR_MOUSE
);
57 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
);
59 static ReusableBuffer
<uint8
> _cursor_backup
;
61 ZoomLevel _gui_zoom
; ///< GUI Zoom level
62 ZoomLevel _font_zoom
; ///< Font Zoom level
64 int8 _gui_zoom_cfg
; ///< GUI zoom level in config.
65 int8 _font_zoom_cfg
; ///< Font zoom level in config.
69 * The rect for repaint.
71 * This rectangle defines the area which should be repaint by the video driver.
75 static Rect _invalid_rect
;
76 static const byte
*_colour_remap_ptr
;
77 static byte _string_colourremap
[3]; ///< Recoloursprite for stringdrawing. The grf loader ensures that #ST_FONT sprites only use colours 0 to 2.
79 static const uint DIRTY_BLOCK_HEIGHT
= 8;
80 static const uint DIRTY_BLOCK_WIDTH
= 64;
82 static uint _dirty_bytes_per_line
= 0;
83 static byte
*_dirty_blocks
= nullptr;
84 extern uint _dirty_block_colour
;
86 void GfxScroll(int left
, int top
, int width
, int height
, int xo
, int yo
)
88 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
90 if (xo
== 0 && yo
== 0) return;
92 if (_cursor
.visible
) UndrawMouseCursor();
94 if (_networking
) NetworkUndrawChatMessage();
96 blitter
->ScrollBuffer(_screen
.dst_ptr
, left
, top
, width
, height
, xo
, yo
);
97 /* This part of the screen is now dirty. */
98 VideoDriver::GetInstance()->MakeDirty(left
, top
, width
, height
);
103 * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
105 * @pre dpi->zoom == ZOOM_LVL_NORMAL, right >= left, bottom >= top
106 * @param left Minimum X (inclusive)
107 * @param top Minimum Y (inclusive)
108 * @param right Maximum X (inclusive)
109 * @param bottom Maximum Y (inclusive)
110 * @param colour A 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR)
112 * FILLRECT_OPAQUE: Fill the rectangle with the specified colour
113 * FILLRECT_CHECKER: Like FILLRECT_OPAQUE, but only draw every second pixel (used to grey out things)
114 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the rectangle currently on screen
116 void GfxFillRect(int left
, int top
, int right
, int bottom
, int colour
, FillRectMode mode
)
118 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
119 const DrawPixelInfo
*dpi
= _cur_dpi
;
121 const int otop
= top
;
122 const int oleft
= left
;
124 if (dpi
->zoom
!= ZOOM_LVL_NORMAL
) return;
125 if (left
> right
|| top
> bottom
) return;
126 if (right
< dpi
->left
|| left
>= dpi
->left
+ dpi
->width
) return;
127 if (bottom
< dpi
->top
|| top
>= dpi
->top
+ dpi
->height
) return;
129 if ( (left
-= dpi
->left
) < 0) left
= 0;
130 right
= right
- dpi
->left
+ 1;
131 if (right
> dpi
->width
) right
= dpi
->width
;
135 if ( (top
-= dpi
->top
) < 0) top
= 0;
136 bottom
= bottom
- dpi
->top
+ 1;
137 if (bottom
> dpi
->height
) bottom
= dpi
->height
;
141 dst
= blitter
->MoveTo(dpi
->dst_ptr
, left
, top
);
144 default: // FILLRECT_OPAQUE
145 blitter
->DrawRect(dst
, right
, bottom
, (uint8
)colour
);
148 case FILLRECT_RECOLOUR
:
149 blitter
->DrawColourMappingRect(dst
, right
, bottom
, GB(colour
, 0, PALETTE_WIDTH
));
152 case FILLRECT_CHECKER
: {
153 byte bo
= (oleft
- left
+ dpi
->left
+ otop
- top
+ dpi
->top
) & 1;
155 for (int i
= (bo
^= 1); i
< right
; i
+= 2) blitter
->SetPixel(dst
, i
, 0, (uint8
)colour
);
156 dst
= blitter
->MoveTo(dst
, 0, 1);
157 } while (--bottom
> 0);
163 typedef std::pair
<Point
, Point
> LineSegment
;
166 * Make line segments from a polygon defined by points, translated by an offset.
167 * Entirely horizontal lines (start and end at same Y coordinate) are skipped, as they are irrelevant to scanline conversion algorithms.
168 * Generated line segments always have the lowest Y coordinate point first, i.e. original direction is lost.
169 * @param shape The polygon to convert.
170 * @param offset Offset vector subtracted from all coordinates in the shape.
171 * @return Vector of undirected line segments.
173 static std::vector
<LineSegment
> MakePolygonSegments(const std::vector
<Point
> &shape
, Point offset
)
175 std::vector
<LineSegment
> segments
;
176 if (shape
.size() < 3) return segments
; // fewer than 3 will always result in an empty polygon
177 segments
.reserve(shape
.size());
179 /* Connect first and last point by having initial previous point be the last */
180 Point prev
= shape
.back();
183 for (Point pt
: shape
) {
186 /* Create segments for all non-horizontal lines in the polygon.
187 * The segments always have lowest Y coordinate first. */
189 segments
.emplace_back(pt
, prev
);
190 } else if (prev
.y
< pt
.y
) {
191 segments
.emplace_back(prev
, pt
);
200 * Fill a polygon with colour.
201 * The odd-even winding rule is used, i.e. self-intersecting polygons will have holes in them.
202 * Left and top edges are inclusive, right and bottom edges are exclusive.
203 * @note For rectangles the GfxFillRect function will be faster.
204 * @pre dpi->zoom == ZOOM_LVL_NORMAL
205 * @param shape List of points on the polygon.
206 * @param colour An 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR).
208 * FILLRECT_OPAQUE: Fill the polygon with the specified colour.
209 * FILLRECT_CHECKER: Fill every other pixel with the specified colour, in a checkerboard pattern.
210 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the polygon.
212 void GfxFillPolygon(const std::vector
<Point
> &shape
, int colour
, FillRectMode mode
)
214 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
215 const DrawPixelInfo
*dpi
= _cur_dpi
;
216 if (dpi
->zoom
!= ZOOM_LVL_NORMAL
) return;
218 std::vector
<LineSegment
> segments
= MakePolygonSegments(shape
, Point
{ dpi
->left
, dpi
->top
});
220 /* Remove segments appearing entirely above or below the clipping area. */
221 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());
223 /* Check that this wasn't an empty shape (all points on a horizontal line or outside clipping.) */
224 if (segments
.empty()) return;
226 /* Sort the segments by first point Y coordinate. */
227 std::sort(segments
.begin(), segments
.end(), [](const LineSegment
&a
, const LineSegment
&b
) { return a
.first
.y
< b
.first
.y
; });
229 /* Segments intersecting current scanline. */
230 std::vector
<LineSegment
> active
;
231 /* Intersection points with a scanline.
232 * Kept outside loop to avoid repeated re-allocations. */
233 std::vector
<int> intersections
;
234 /* Normal, reasonable polygons don't have many intersections per scanline. */
236 intersections
.reserve(4);
238 /* Scan through the segments and paint each scanline. */
239 int y
= segments
.front().first
.y
;
240 std::vector
<LineSegment
>::iterator nextseg
= segments
.begin();
241 while (!active
.empty() || nextseg
!= segments
.end()) {
242 /* Clean up segments that have ended. */
243 active
.erase(std::remove_if(active
.begin(), active
.end(), [y
](const LineSegment
&s
) { return s
.second
.y
== y
; }), active
.end());
245 /* Activate all segments starting on this scanline. */
246 while (nextseg
!= segments
.end() && nextseg
->first
.y
== y
) {
247 active
.push_back(*nextseg
);
251 /* Check clipping. */
256 if (y
>= dpi
->height
) return;
258 /* Intersect scanline with all active segments. */
259 intersections
.clear();
260 for (const LineSegment
&s
: active
) {
261 const int sdx
= s
.second
.x
- s
.first
.x
;
262 const int sdy
= s
.second
.y
- s
.first
.y
;
263 const int ldy
= y
- s
.first
.y
;
264 const int x
= s
.first
.x
+ sdx
* ldy
/ sdy
;
265 intersections
.push_back(x
);
268 /* Fill between pairs of intersections. */
269 std::sort(intersections
.begin(), intersections
.end());
270 for (size_t i
= 1; i
< intersections
.size(); i
+= 2) {
271 /* Check clipping. */
272 const int x1
= std::max(0, intersections
[i
- 1]);
273 const int x2
= std::min(intersections
[i
], dpi
->width
);
274 if (x2
< 0) continue;
275 if (x1
>= dpi
->width
) continue;
277 /* Fill line y from x1 to x2. */
278 void *dst
= blitter
->MoveTo(dpi
->dst_ptr
, x1
, y
);
280 default: // FILLRECT_OPAQUE
281 blitter
->DrawRect(dst
, x2
- x1
, 1, (uint8
)colour
);
283 case FILLRECT_RECOLOUR
:
284 blitter
->DrawColourMappingRect(dst
, x2
- x1
, 1, GB(colour
, 0, PALETTE_WIDTH
));
286 case FILLRECT_CHECKER
:
287 /* Fill every other pixel, offset such that the sum of filled pixels' X and Y coordinates is odd.
288 * This creates a checkerboard effect. */
289 for (int x
= (x1
+ y
) & 1; x
< x2
- x1
; x
+= 2) {
290 blitter
->SetPixel(dst
, x
, 0, (uint8
)colour
);
302 * Check line clipping by using a linear equation and draw the visible part of
303 * the line given by x/y and x2/y2.
304 * @param video Destination pointer to draw into.
305 * @param x X coordinate of first point.
306 * @param y Y coordinate of first point.
307 * @param x2 X coordinate of second point.
308 * @param y2 Y coordinate of second point.
309 * @param screen_width With of the screen to check clipping against.
310 * @param screen_height Height of the screen to check clipping against.
311 * @param colour Colour of the line.
312 * @param width Width of the line.
313 * @param dash Length of dashes for dashed lines. 0 means solid line.
315 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)
317 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
321 if (y2
== y
|| x2
== x
) {
322 /* Special case: horizontal/vertical line. All checks already done in GfxPreprocessLine. */
323 blitter
->DrawLine(video
, x
, y
, x2
, y2
, screen_width
, screen_height
, colour
, width
, dash
);
327 int grade_y
= y2
- y
;
328 int grade_x
= x2
- x
;
330 /* Clipping rectangle. Slightly extended so we can ignore the width of the line. */
331 int extra
= (int)CeilDiv(3 * width
, 4); // not less then "width * sqrt(2) / 2"
332 Rect clip
= { -extra
, -extra
, screen_width
- 1 + extra
, screen_height
- 1 + extra
};
334 /* prevent integer overflows. */
336 while (INT_MAX
/ abs(grade_y
) < std::max(abs(clip
.left
- x
), abs(clip
.right
- x
))) {
339 margin
*= 2; // account for rounding errors
342 /* Imagine that the line is infinitely long and it intersects with
343 * infinitely long left and right edges of the clipping rectangle.
344 * If both intersection points are outside the clipping rectangle
345 * and both on the same side of it, we don't need to draw anything. */
346 int left_isec_y
= y
+ (clip
.left
- x
) * grade_y
/ grade_x
;
347 int right_isec_y
= y
+ (clip
.right
- x
) * grade_y
/ grade_x
;
348 if ((left_isec_y
> clip
.bottom
+ margin
&& right_isec_y
> clip
.bottom
+ margin
) ||
349 (left_isec_y
< clip
.top
- margin
&& right_isec_y
< clip
.top
- margin
)) {
353 /* It is possible to use the line equation to further reduce the amount of
354 * work the blitter has to do by shortening the effective line segment.
355 * However, in order to get that right and prevent the flickering effects
356 * of rounding errors so much additional code has to be run here that in
357 * the general case the effect is not noticeable. */
359 blitter
->DrawLine(video
, x
, y
, x2
, y2
, screen_width
, screen_height
, colour
, width
, dash
);
363 * Align parameters of a line to the given DPI and check simple clipping.
364 * @param dpi Screen parameters to align with.
365 * @param x X coordinate of first point.
366 * @param y Y coordinate of first point.
367 * @param x2 X coordinate of second point.
368 * @param y2 Y coordinate of second point.
369 * @param width Width of the line.
370 * @return True if the line is likely to be visible, false if it's certainly
373 static inline bool GfxPreprocessLine(DrawPixelInfo
*dpi
, int &x
, int &y
, int &x2
, int &y2
, int width
)
380 /* Check simple clipping */
381 if (x
+ width
/ 2 < 0 && x2
+ width
/ 2 < 0 ) return false;
382 if (y
+ width
/ 2 < 0 && y2
+ width
/ 2 < 0 ) return false;
383 if (x
- width
/ 2 > dpi
->width
&& x2
- width
/ 2 > dpi
->width
) return false;
384 if (y
- width
/ 2 > dpi
->height
&& y2
- width
/ 2 > dpi
->height
) return false;
388 void GfxDrawLine(int x
, int y
, int x2
, int y2
, int colour
, int width
, int dash
)
390 DrawPixelInfo
*dpi
= _cur_dpi
;
391 if (GfxPreprocessLine(dpi
, x
, y
, x2
, y2
, width
)) {
392 GfxDoDrawLine(dpi
->dst_ptr
, x
, y
, x2
, y2
, dpi
->width
, dpi
->height
, colour
, width
, dash
);
396 void GfxDrawLineUnscaled(int x
, int y
, int x2
, int y2
, int colour
)
398 DrawPixelInfo
*dpi
= _cur_dpi
;
399 if (GfxPreprocessLine(dpi
, x
, y
, x2
, y2
, 1)) {
400 GfxDoDrawLine(dpi
->dst_ptr
,
401 UnScaleByZoom(x
, dpi
->zoom
), UnScaleByZoom(y
, dpi
->zoom
),
402 UnScaleByZoom(x2
, dpi
->zoom
), UnScaleByZoom(y2
, dpi
->zoom
),
403 UnScaleByZoom(dpi
->width
, dpi
->zoom
), UnScaleByZoom(dpi
->height
, dpi
->zoom
), colour
, 1);
408 * Draws the projection of a parallelepiped.
409 * This can be used to draw boxes in world coordinates.
411 * @param x Screen X-coordinate of top front corner.
412 * @param y Screen Y-coordinate of top front corner.
413 * @param dx1 Screen X-length of first edge.
414 * @param dy1 Screen Y-length of first edge.
415 * @param dx2 Screen X-length of second edge.
416 * @param dy2 Screen Y-length of second edge.
417 * @param dx3 Screen X-length of third edge.
418 * @param dy3 Screen Y-length of third edge.
420 void DrawBox(int x
, int y
, int dx1
, int dy1
, int dx2
, int dy2
, int dx3
, int dy3
)
426 * <--__(dx1,dy1) /(dx2,dy2)
437 static const byte colour
= PC_WHITE
;
439 GfxDrawLineUnscaled(x
, y
, x
+ dx1
, y
+ dy1
, colour
);
440 GfxDrawLineUnscaled(x
, y
, x
+ dx2
, y
+ dy2
, colour
);
441 GfxDrawLineUnscaled(x
, y
, x
+ dx3
, y
+ dy3
, colour
);
443 GfxDrawLineUnscaled(x
+ dx1
, y
+ dy1
, x
+ dx1
+ dx2
, y
+ dy1
+ dy2
, colour
);
444 GfxDrawLineUnscaled(x
+ dx1
, y
+ dy1
, x
+ dx1
+ dx3
, y
+ dy1
+ dy3
, colour
);
445 GfxDrawLineUnscaled(x
+ dx2
, y
+ dy2
, x
+ dx2
+ dx1
, y
+ dy2
+ dy1
, colour
);
446 GfxDrawLineUnscaled(x
+ dx2
, y
+ dy2
, x
+ dx2
+ dx3
, y
+ dy2
+ dy3
, colour
);
447 GfxDrawLineUnscaled(x
+ dx3
, y
+ dy3
, x
+ dx3
+ dx1
, y
+ dy3
+ dy1
, colour
);
448 GfxDrawLineUnscaled(x
+ dx3
, y
+ dy3
, x
+ dx3
+ dx2
, y
+ dy3
+ dy2
, colour
);
452 * Set the colour remap to be for the given colour.
453 * @param colour the new colour of the remap.
455 static void SetColourRemap(TextColour colour
)
457 if (colour
== TC_INVALID
) return;
459 /* Black strings have no shading ever; the shading is black, so it
460 * would be invisible at best, but it actually makes it illegible. */
461 bool no_shade
= (colour
& TC_NO_SHADE
) != 0 || colour
== TC_BLACK
;
462 bool raw_colour
= (colour
& TC_IS_PALETTE_COLOUR
) != 0;
463 colour
&= ~(TC_NO_SHADE
| TC_IS_PALETTE_COLOUR
| TC_FORCED
);
465 _string_colourremap
[1] = raw_colour
? (byte
)colour
: _string_colourmap
[colour
];
466 _string_colourremap
[2] = no_shade
? 0 : 1;
467 _colour_remap_ptr
= _string_colourremap
;
471 * Drawing routine for drawing a laid out line of text.
472 * @param line String to draw.
473 * @param y The top most position to draw on.
474 * @param left The left most position to draw on.
475 * @param right The right most position to draw on.
476 * @param align The alignment of the string when drawing left-to-right. In the
477 * case a right-to-left language is chosen this is inverted so it
478 * will be drawn in the right direction.
479 * @param underline Whether to underline what has been drawn or not.
480 * @param truncation Whether to perform string truncation or not.
482 * @return In case of left or center alignment the right most pixel we have drawn to.
483 * In case of right alignment the left most pixel we have drawn to.
485 static int DrawLayoutLine(const ParagraphLayouter::Line
&line
, int y
, int left
, int right
, StringAlignment align
, bool underline
, bool truncation
)
487 if (line
.CountRuns() == 0) return 0;
489 int w
= line
.GetWidth();
490 int h
= line
.GetLeading();
493 * The following is needed for truncation.
494 * Depending on the text direction, we either remove bits at the rear
495 * or the front. For this we shift the entire area to draw so it fits
496 * within the left/right bounds and the side we do not truncate it on.
497 * Then we determine the truncation location, i.e. glyphs that fall
498 * outside of the range min_x - max_x will not be drawn; they are thus
499 * the truncated glyphs.
501 * At a later step we insert the dots.
504 int max_w
= right
- left
+ 1; // The maximum width.
506 int offset_x
= 0; // The offset we need for positioning the glyphs
507 int min_x
= left
; // The minimum x position to draw normal glyphs on.
508 int max_x
= right
; // The maximum x position to draw normal glyphs on.
510 truncation
&= max_w
< w
; // Whether we need to do truncation.
511 int dot_width
= 0; // Cache for the width of the dot.
512 const Sprite
*dot_sprite
= nullptr; // Cache for the sprite of the dot.
516 * Assumption may be made that all fonts of a run are of the same size.
517 * In any case, we'll use these dots for the abbreviation, so even if
518 * another size would be chosen it won't have truncated too little for
519 * the truncation dots.
521 FontCache
*fc
= ((const Font
*)line
.GetVisualRun(0).GetFont())->fc
;
522 GlyphID dot_glyph
= fc
->MapCharToGlyph('.');
523 dot_width
= fc
->GetGlyphWidth(dot_glyph
);
524 dot_sprite
= fc
->GetGlyph(dot_glyph
);
526 if (_current_text_dir
== TD_RTL
) {
527 min_x
+= 3 * dot_width
;
528 offset_x
= w
- 3 * dot_width
- max_w
;
530 max_x
-= 3 * dot_width
;
536 /* In case we have a RTL language we swap the alignment. */
537 if (!(align
& SA_FORCE
) && _current_text_dir
== TD_RTL
&& (align
& SA_HOR_MASK
) != SA_HOR_CENTER
) align
^= SA_RIGHT
;
539 /* right is the right most position to draw on. In this case we want to do
540 * calculations with the width of the string. In comparison right can be
541 * seen as lastof(todraw) and width as lengthof(todraw). They differ by 1.
542 * So most +1/-1 additions are to move from lengthof to 'indices'.
544 switch (align
& SA_HOR_MASK
) {
546 /* right + 1 = left + w */
547 right
= left
+ w
- 1;
551 left
= RoundDivSU(right
+ 1 + left
- w
, 2);
552 /* right + 1 = left + w */
553 right
= left
+ w
- 1;
557 left
= right
+ 1 - w
;
564 TextColour colour
= TC_BLACK
;
565 bool draw_shadow
= false;
566 for (int run_index
= 0; run_index
< line
.CountRuns(); run_index
++) {
567 const ParagraphLayouter::VisualRun
&run
= line
.GetVisualRun(run_index
);
568 const Font
*f
= (const Font
*)run
.GetFont();
570 FontCache
*fc
= f
->fc
;
572 SetColourRemap(colour
);
574 DrawPixelInfo
*dpi
= _cur_dpi
;
575 int dpi_left
= dpi
->left
;
576 int dpi_right
= dpi
->left
+ dpi
->width
- 1;
578 draw_shadow
= fc
->GetDrawGlyphShadow() && (colour
& TC_NO_SHADE
) == 0 && colour
!= TC_BLACK
;
580 for (int i
= 0; i
< run
.GetGlyphCount(); i
++) {
581 GlyphID glyph
= run
.GetGlyphs()[i
];
583 /* Not a valid glyph (empty) */
584 if (glyph
== 0xFFFF) continue;
586 int begin_x
= (int)run
.GetPositions()[i
* 2] + left
- offset_x
;
587 int end_x
= (int)run
.GetPositions()[i
* 2 + 2] + left
- offset_x
- 1;
588 int top
= (int)run
.GetPositions()[i
* 2 + 1] + y
;
590 /* Truncated away. */
591 if (truncation
&& (begin_x
< min_x
|| end_x
> max_x
)) continue;
593 const Sprite
*sprite
= fc
->GetGlyph(glyph
);
594 /* Check clipping (the "+ 1" is for the shadow). */
595 if (begin_x
+ sprite
->x_offs
> dpi_right
|| begin_x
+ sprite
->x_offs
+ sprite
->width
/* - 1 + 1 */ < dpi_left
) continue;
597 if (draw_shadow
&& (glyph
& SPRITE_GLYPH
) == 0) {
598 SetColourRemap(TC_BLACK
);
599 GfxMainBlitter(sprite
, begin_x
+ 1, top
+ 1, BM_COLOUR_REMAP
);
600 SetColourRemap(colour
);
602 GfxMainBlitter(sprite
, begin_x
, top
, BM_COLOUR_REMAP
);
607 int x
= (_current_text_dir
== TD_RTL
) ? left
: (right
- 3 * dot_width
);
608 for (int i
= 0; i
< 3; i
++, x
+= dot_width
) {
610 SetColourRemap(TC_BLACK
);
611 GfxMainBlitter(dot_sprite
, x
+ 1, y
+ 1, BM_COLOUR_REMAP
);
612 SetColourRemap(colour
);
614 GfxMainBlitter(dot_sprite
, x
, y
, BM_COLOUR_REMAP
);
619 GfxFillRect(left
, y
+ h
, right
, y
+ h
, _string_colourremap
[1]);
622 return (align
& SA_HOR_MASK
) == SA_RIGHT
? left
: right
;
626 * Draw string, possibly truncated to make it fit in its allocated space
628 * @param left The left most position to draw on.
629 * @param right The right most position to draw on.
630 * @param top The top most position to draw on.
631 * @param str String to draw.
632 * @param colour Colour used for drawing the string, for details see _string_colourmap in
633 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
634 * @param align The alignment of the string when drawing left-to-right. In the
635 * case a right-to-left language is chosen this is inverted so it
636 * will be drawn in the right direction.
637 * @param underline Whether to underline what has been drawn or not.
638 * @param fontsize The size of the initial characters.
639 * @return In case of left or center alignment the right most pixel we have drawn to.
640 * In case of right alignment the left most pixel we have drawn to.
642 int DrawString(int left
, int right
, int top
, const char *str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
644 /* The string may contain control chars to change the font, just use the biggest font for clipping. */
645 int max_height
= std::max({FONT_HEIGHT_SMALL
, FONT_HEIGHT_NORMAL
, FONT_HEIGHT_LARGE
, FONT_HEIGHT_MONO
});
647 /* Funny glyphs may extent outside the usual bounds, so relax the clipping somewhat. */
648 int extra
= max_height
/ 2;
650 if (_cur_dpi
->top
+ _cur_dpi
->height
+ extra
< top
|| _cur_dpi
->top
> top
+ max_height
+ extra
||
651 _cur_dpi
->left
+ _cur_dpi
->width
+ extra
< left
|| _cur_dpi
->left
> right
+ extra
) {
655 Layouter
layout(str
, INT32_MAX
, colour
, fontsize
);
656 if (layout
.size() == 0) return 0;
658 return DrawLayoutLine(*layout
.front(), top
, left
, right
, align
, underline
, true);
662 * Draw string, possibly truncated to make it fit in its allocated space
664 * @param left The left most position to draw on.
665 * @param right The right most position to draw on.
666 * @param top The top most position to draw on.
667 * @param str String to draw.
668 * @param colour Colour used for drawing the string, for details see _string_colourmap in
669 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
670 * @param align The alignment of the string when drawing left-to-right. In the
671 * case a right-to-left language is chosen this is inverted so it
672 * will be drawn in the right direction.
673 * @param underline Whether to underline what has been drawn or not.
674 * @param fontsize The size of the initial characters.
675 * @return In case of left or center alignment the right most pixel we have drawn to.
676 * In case of right alignment the left most pixel we have drawn to.
678 int DrawString(int left
, int right
, int top
, const std::string
&str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
680 return DrawString(left
, right
, top
, str
.c_str(), colour
, align
, underline
, fontsize
);
684 * Draw string, possibly truncated to make it fit in its allocated space
686 * @param left The left most position to draw on.
687 * @param right The right most position to draw on.
688 * @param top The top most position to draw on.
689 * @param str String to draw.
690 * @param colour Colour used for drawing the string, for details see _string_colourmap in
691 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
692 * @param align The alignment of the string when drawing left-to-right. In the
693 * case a right-to-left language is chosen this is inverted so it
694 * will be drawn in the right direction.
695 * @param underline Whether to underline what has been drawn or not.
696 * @param fontsize The size of the initial characters.
697 * @return In case of left or center alignment the right most pixel we have drawn to.
698 * In case of right alignment the left most pixel we have drawn to.
700 int DrawString(int left
, int right
, int top
, StringID str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
702 char buffer
[DRAW_STRING_BUFFER
];
703 GetString(buffer
, str
, lastof(buffer
));
704 return DrawString(left
, right
, top
, buffer
, colour
, align
, underline
, fontsize
);
708 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
709 * @param str string to check
710 * @param maxw maximum string width
711 * @return height of pixels of string when it is drawn
713 int GetStringHeight(const char *str
, int maxw
, FontSize fontsize
)
715 Layouter
layout(str
, maxw
, TC_FROMSTRING
, fontsize
);
716 return layout
.GetBounds().height
;
720 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
721 * @param str string to check
722 * @param maxw maximum string width
723 * @return height of pixels of string when it is drawn
725 int GetStringHeight(StringID str
, int maxw
)
727 char buffer
[DRAW_STRING_BUFFER
];
728 GetString(buffer
, str
, lastof(buffer
));
729 return GetStringHeight(buffer
, maxw
);
733 * Calculates number of lines of string. The string is changed to a multiline string if needed.
734 * @param str string to check
735 * @param maxw maximum string width
736 * @return number of lines of string when it is drawn
738 int GetStringLineCount(StringID str
, int maxw
)
740 char buffer
[DRAW_STRING_BUFFER
];
741 GetString(buffer
, str
, lastof(buffer
));
743 Layouter
layout(buffer
, maxw
);
744 return (uint
)layout
.size();
748 * Calculate string bounding box for multi-line strings.
749 * @param str String to check.
750 * @param suggestion Suggested bounding box.
751 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
753 Dimension
GetStringMultiLineBoundingBox(StringID str
, const Dimension
&suggestion
)
755 Dimension box
= {suggestion
.width
, (uint
)GetStringHeight(str
, suggestion
.width
)};
760 * Calculate string bounding box for multi-line strings.
761 * @param str String to check.
762 * @param suggestion Suggested bounding box.
763 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
765 Dimension
GetStringMultiLineBoundingBox(const char *str
, const Dimension
&suggestion
)
767 Dimension box
= {suggestion
.width
, (uint
)GetStringHeight(str
, suggestion
.width
)};
772 * Draw string, possibly over multiple lines.
774 * @param left The left most position to draw on.
775 * @param right The right most position to draw on.
776 * @param top The top most position to draw on.
777 * @param bottom The bottom most position to draw on.
778 * @param str String to draw.
779 * @param colour Colour used for drawing the string, for details see _string_colourmap in
780 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
781 * @param align The horizontal and vertical alignment of the string.
782 * @param underline Whether to underline all strings
783 * @param fontsize The size of the initial characters.
785 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
787 int DrawStringMultiLine(int left
, int right
, int top
, int bottom
, const char *str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
789 int maxw
= right
- left
+ 1;
790 int maxh
= bottom
- top
+ 1;
792 /* It makes no sense to even try if it can't be drawn anyway, or
793 * do we really want to support fonts of 0 or less pixels high? */
794 if (maxh
<= 0) return top
;
796 Layouter
layout(str
, maxw
, colour
, fontsize
);
797 int total_height
= layout
.GetBounds().height
;
799 switch (align
& SA_VERT_MASK
) {
805 y
= RoundDivSU(bottom
+ top
- total_height
, 2);
809 y
= bottom
- total_height
;
812 default: NOT_REACHED();
816 int first_line
= bottom
;
818 for (const auto &line
: layout
) {
820 int line_height
= line
->GetLeading();
821 if (y
>= top
&& y
< bottom
) {
822 last_line
= y
+ line_height
;
823 if (first_line
> y
) first_line
= y
;
825 DrawLayoutLine(*line
, y
, left
, right
, align
, underline
, false);
830 return ((align
& SA_VERT_MASK
) == SA_BOTTOM
) ? first_line
: last_line
;
835 * Draw string, possibly over multiple lines.
837 * @param left The left most position to draw on.
838 * @param right The right most position to draw on.
839 * @param top The top most position to draw on.
840 * @param bottom The bottom most position to draw on.
841 * @param str String to draw.
842 * @param colour Colour used for drawing the string, for details see _string_colourmap in
843 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
844 * @param align The horizontal and vertical alignment of the string.
845 * @param underline Whether to underline all strings
846 * @param fontsize The size of the initial characters.
848 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
850 int DrawStringMultiLine(int left
, int right
, int top
, int bottom
, const std::string
&str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
852 return DrawStringMultiLine(left
, right
, top
, bottom
, str
.c_str(), colour
, align
, underline
, fontsize
);
856 * Draw string, possibly over multiple lines.
858 * @param left The left most position to draw on.
859 * @param right The right most position to draw on.
860 * @param top The top most position to draw on.
861 * @param bottom The bottom most position to draw on.
862 * @param str String to draw.
863 * @param colour Colour used for drawing the string, for details see _string_colourmap in
864 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
865 * @param align The horizontal and vertical alignment of the string.
866 * @param underline Whether to underline all strings
867 * @param fontsize The size of the initial characters.
869 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
871 int DrawStringMultiLine(int left
, int right
, int top
, int bottom
, StringID str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
873 char buffer
[DRAW_STRING_BUFFER
];
874 GetString(buffer
, str
, lastof(buffer
));
875 return DrawStringMultiLine(left
, right
, top
, bottom
, buffer
, colour
, align
, underline
, fontsize
);
879 * Return the string dimension in pixels. The height and width are returned
880 * in a single Dimension value. TINYFONT, BIGFONT modifiers are only
881 * supported as the first character of the string. The returned dimensions
882 * are therefore a rough estimation correct for all the current strings
883 * but not every possible combination
884 * @param str string to calculate pixel-width
885 * @param start_fontsize Fontsize to start the text with
886 * @return string width and height in pixels
888 Dimension
GetStringBoundingBox(const char *str
, FontSize start_fontsize
)
890 Layouter
layout(str
, INT32_MAX
, TC_FROMSTRING
, start_fontsize
);
891 return layout
.GetBounds();
895 * Return the string dimension in pixels. The height and width are returned
896 * in a single Dimension value. TINYFONT, BIGFONT modifiers are only
897 * supported as the first character of the string. The returned dimensions
898 * are therefore a rough estimation correct for all the current strings
899 * but not every possible combination
900 * @param str string to calculate pixel-width
901 * @param start_fontsize Fontsize to start the text with
902 * @return string width and height in pixels
904 Dimension
GetStringBoundingBox(const std::string
&str
, FontSize start_fontsize
)
906 return GetStringBoundingBox(str
.c_str(), start_fontsize
);
910 * Get bounding box of a string. Uses parameters set by #SetDParam if needed.
911 * Has the same restrictions as #GetStringBoundingBox(const char *str, FontSize start_fontsize).
912 * @param strid String to examine.
913 * @return Width and height of the bounding box for the string in pixels.
915 Dimension
GetStringBoundingBox(StringID strid
)
917 char buffer
[DRAW_STRING_BUFFER
];
919 GetString(buffer
, strid
, lastof(buffer
));
920 return GetStringBoundingBox(buffer
);
924 * Get the leading corner of a character in a single-line string relative
925 * to the start of the string.
926 * @param str String containing the character.
927 * @param ch Pointer to the character in the string.
928 * @param start_fontsize Font size to start the text with.
929 * @return Upper left corner of the glyph associated with the character.
931 Point
GetCharPosInString(const char *str
, const char *ch
, FontSize start_fontsize
)
933 Layouter
layout(str
, INT32_MAX
, TC_FROMSTRING
, start_fontsize
);
934 return layout
.GetCharPosition(ch
);
938 * Get the character from a string that is drawn at a specific position.
939 * @param str String to test.
940 * @param x Position relative to the start of the string.
941 * @param start_fontsize Font size to start the text with.
942 * @return Pointer to the character at the position or nullptr if there is no character at the position.
944 const char *GetCharAtPosition(const char *str
, int x
, FontSize start_fontsize
)
946 if (x
< 0) return nullptr;
948 Layouter
layout(str
, INT32_MAX
, TC_FROMSTRING
, start_fontsize
);
949 return layout
.GetCharAtPosition(x
);
953 * Draw single character horizontally centered around (x,y)
954 * @param c Character (glyph) to draw
955 * @param r Rectangle to draw character within
956 * @param colour Colour to use, for details see _string_colourmap in
957 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
959 void DrawCharCentered(WChar c
, const Rect
&r
, TextColour colour
)
961 SetColourRemap(colour
);
962 GfxMainBlitter(GetGlyph(FS_NORMAL
, c
),
963 CenterBounds(r
.left
, r
.right
, GetCharacterWidth(FS_NORMAL
, c
)),
964 CenterBounds(r
.top
, r
.bottom
, FONT_HEIGHT_NORMAL
),
969 * Get the size of a sprite.
970 * @param sprid Sprite to examine.
971 * @param[out] offset Optionally returns the sprite position offset.
972 * @param zoom The zoom level applicable to the sprite.
973 * @return Sprite size in pixels.
974 * @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.
976 Dimension
GetSpriteSize(SpriteID sprid
, Point
*offset
, ZoomLevel zoom
)
978 const Sprite
*sprite
= GetSprite(sprid
, ST_NORMAL
);
980 if (offset
!= nullptr) {
981 offset
->x
= UnScaleByZoom(sprite
->x_offs
, zoom
);
982 offset
->y
= UnScaleByZoom(sprite
->y_offs
, zoom
);
986 d
.width
= std::max
<int>(0, UnScaleByZoom(sprite
->x_offs
+ sprite
->width
, zoom
));
987 d
.height
= std::max
<int>(0, UnScaleByZoom(sprite
->y_offs
+ sprite
->height
, zoom
));
992 * Helper function to get the blitter mode for different types of palettes.
993 * @param pal The palette to get the blitter mode for.
994 * @return The blitter mode associated with the palette.
996 static BlitterMode
GetBlitterMode(PaletteID pal
)
999 case PAL_NONE
: return BM_NORMAL
;
1000 case PALETTE_CRASH
: return BM_CRASH_REMAP
;
1001 case PALETTE_ALL_BLACK
: return BM_BLACK_REMAP
;
1002 default: return BM_COLOUR_REMAP
;
1007 * Draw a sprite in a viewport.
1008 * @param img Image number to draw
1009 * @param pal Palette to use.
1010 * @param x Left coordinate of image in viewport, scaled by zoom
1011 * @param y Top coordinate of image in viewport, scaled by zoom
1012 * @param sub If available, draw only specified part of the sprite
1014 void DrawSpriteViewport(SpriteID img
, PaletteID pal
, int x
, int y
, const SubSprite
*sub
)
1016 SpriteID real_sprite
= GB(img
, 0, SPRITE_WIDTH
);
1017 if (HasBit(img
, PALETTE_MODIFIER_TRANSPARENT
)) {
1018 _colour_remap_ptr
= GetNonSprite(GB(pal
, 0, PALETTE_WIDTH
), ST_RECOLOUR
) + 1;
1019 GfxMainBlitterViewport(GetSprite(real_sprite
, ST_NORMAL
), x
, y
, BM_TRANSPARENT
, sub
, real_sprite
);
1020 } else if (pal
!= PAL_NONE
) {
1021 if (HasBit(pal
, PALETTE_TEXT_RECOLOUR
)) {
1022 SetColourRemap((TextColour
)GB(pal
, 0, PALETTE_WIDTH
));
1024 _colour_remap_ptr
= GetNonSprite(GB(pal
, 0, PALETTE_WIDTH
), ST_RECOLOUR
) + 1;
1026 GfxMainBlitterViewport(GetSprite(real_sprite
, ST_NORMAL
), x
, y
, GetBlitterMode(pal
), sub
, real_sprite
);
1028 GfxMainBlitterViewport(GetSprite(real_sprite
, ST_NORMAL
), x
, y
, BM_NORMAL
, sub
, real_sprite
);
1033 * Draw a sprite, not in a viewport
1034 * @param img Image number to draw
1035 * @param pal Palette to use.
1036 * @param x Left coordinate of image in pixels
1037 * @param y Top coordinate of image in pixels
1038 * @param sub If available, draw only specified part of the sprite
1039 * @param zoom Zoom level of sprite
1041 void DrawSprite(SpriteID img
, PaletteID pal
, int x
, int y
, const SubSprite
*sub
, ZoomLevel zoom
)
1043 SpriteID real_sprite
= GB(img
, 0, SPRITE_WIDTH
);
1044 if (HasBit(img
, PALETTE_MODIFIER_TRANSPARENT
)) {
1045 _colour_remap_ptr
= GetNonSprite(GB(pal
, 0, PALETTE_WIDTH
), ST_RECOLOUR
) + 1;
1046 GfxMainBlitter(GetSprite(real_sprite
, ST_NORMAL
), x
, y
, BM_TRANSPARENT
, sub
, real_sprite
, zoom
);
1047 } else if (pal
!= PAL_NONE
) {
1048 if (HasBit(pal
, PALETTE_TEXT_RECOLOUR
)) {
1049 SetColourRemap((TextColour
)GB(pal
, 0, PALETTE_WIDTH
));
1051 _colour_remap_ptr
= GetNonSprite(GB(pal
, 0, PALETTE_WIDTH
), ST_RECOLOUR
) + 1;
1053 GfxMainBlitter(GetSprite(real_sprite
, ST_NORMAL
), x
, y
, GetBlitterMode(pal
), sub
, real_sprite
, zoom
);
1055 GfxMainBlitter(GetSprite(real_sprite
, ST_NORMAL
), x
, y
, BM_NORMAL
, sub
, real_sprite
, zoom
);
1060 * The code for setting up the blitter mode and sprite information before finally drawing the sprite.
1061 * @param sprite The sprite to draw.
1062 * @param x The X location to draw.
1063 * @param y The Y location to draw.
1064 * @param mode The settings for the blitter to pass.
1065 * @param sub Whether to only draw a sub set of the sprite.
1066 * @param zoom The zoom level at which to draw the sprites.
1067 * @tparam ZOOM_BASE The factor required to get the sub sprite information into the right size.
1068 * @tparam SCALED_XY Whether the X and Y are scaled or unscaled.
1070 template <int ZOOM_BASE
, bool SCALED_XY
>
1071 static void GfxBlitter(const Sprite
* const sprite
, int x
, int y
, BlitterMode mode
, const SubSprite
* const sub
, SpriteID sprite_id
, ZoomLevel zoom
)
1073 const DrawPixelInfo
*dpi
= _cur_dpi
;
1074 Blitter::BlitterParams bp
;
1078 x
= ScaleByZoom(x
, zoom
);
1079 y
= ScaleByZoom(y
, zoom
);
1082 /* Move to the correct offset */
1083 x
+= sprite
->x_offs
;
1084 y
+= sprite
->y_offs
;
1086 if (sub
== nullptr) {
1090 bp
.width
= UnScaleByZoom(sprite
->width
, zoom
);
1091 bp
.height
= UnScaleByZoom(sprite
->height
, zoom
);
1093 /* Amount of pixels to clip from the source sprite */
1094 int clip_left
= std::max(0, -sprite
->x_offs
+ sub
->left
* ZOOM_BASE
);
1095 int clip_top
= std::max(0, -sprite
->y_offs
+ sub
->top
* ZOOM_BASE
);
1096 int clip_right
= std::max(0, sprite
->width
- (-sprite
->x_offs
+ (sub
->right
+ 1) * ZOOM_BASE
));
1097 int clip_bottom
= std::max(0, sprite
->height
- (-sprite
->y_offs
+ (sub
->bottom
+ 1) * ZOOM_BASE
));
1099 if (clip_left
+ clip_right
>= sprite
->width
) return;
1100 if (clip_top
+ clip_bottom
>= sprite
->height
) return;
1102 bp
.skip_left
= UnScaleByZoomLower(clip_left
, zoom
);
1103 bp
.skip_top
= UnScaleByZoomLower(clip_top
, zoom
);
1104 bp
.width
= UnScaleByZoom(sprite
->width
- clip_left
- clip_right
, zoom
);
1105 bp
.height
= UnScaleByZoom(sprite
->height
- clip_top
- clip_bottom
, zoom
);
1107 x
+= ScaleByZoom(bp
.skip_left
, zoom
);
1108 y
+= ScaleByZoom(bp
.skip_top
, zoom
);
1111 /* Copy the main data directly from the sprite */
1112 bp
.sprite
= sprite
->data
;
1113 bp
.sprite_width
= sprite
->width
;
1114 bp
.sprite_height
= sprite
->height
;
1118 bp
.dst
= dpi
->dst_ptr
;
1119 bp
.pitch
= dpi
->pitch
;
1120 bp
.remap
= _colour_remap_ptr
;
1122 assert(sprite
->width
> 0);
1123 assert(sprite
->height
> 0);
1125 if (bp
.width
<= 0) return;
1126 if (bp
.height
<= 0) return;
1128 y
-= SCALED_XY
? ScaleByZoom(dpi
->top
, zoom
) : dpi
->top
;
1129 int y_unscaled
= UnScaleByZoom(y
, zoom
);
1130 /* Check for top overflow */
1132 bp
.height
-= -y_unscaled
;
1133 if (bp
.height
<= 0) return;
1134 bp
.skip_top
+= -y_unscaled
;
1137 bp
.top
= y_unscaled
;
1140 /* Check for bottom overflow */
1141 y
+= SCALED_XY
? ScaleByZoom(bp
.height
- dpi
->height
, zoom
) : ScaleByZoom(bp
.height
, zoom
) - dpi
->height
;
1143 bp
.height
-= UnScaleByZoom(y
, zoom
);
1144 if (bp
.height
<= 0) return;
1147 x
-= SCALED_XY
? ScaleByZoom(dpi
->left
, zoom
) : dpi
->left
;
1148 int x_unscaled
= UnScaleByZoom(x
, zoom
);
1149 /* Check for left overflow */
1151 bp
.width
-= -x_unscaled
;
1152 if (bp
.width
<= 0) return;
1153 bp
.skip_left
+= -x_unscaled
;
1156 bp
.left
= x_unscaled
;
1159 /* Check for right overflow */
1160 x
+= SCALED_XY
? ScaleByZoom(bp
.width
- dpi
->width
, zoom
) : ScaleByZoom(bp
.width
, zoom
) - dpi
->width
;
1162 bp
.width
-= UnScaleByZoom(x
, zoom
);
1163 if (bp
.width
<= 0) return;
1166 assert(bp
.skip_left
+ bp
.width
<= UnScaleByZoom(sprite
->width
, zoom
));
1167 assert(bp
.skip_top
+ bp
.height
<= UnScaleByZoom(sprite
->height
, zoom
));
1169 /* We do not want to catch the mouse. However we also use that spritenumber for unknown (text) sprites. */
1170 if (_newgrf_debug_sprite_picker
.mode
== SPM_REDRAW
&& sprite_id
!= SPR_CURSOR_MOUSE
) {
1171 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1172 void *topleft
= blitter
->MoveTo(bp
.dst
, bp
.left
, bp
.top
);
1173 void *bottomright
= blitter
->MoveTo(topleft
, bp
.width
- 1, bp
.height
- 1);
1175 void *clicked
= _newgrf_debug_sprite_picker
.clicked_pixel
;
1177 if (topleft
<= clicked
&& clicked
<= bottomright
) {
1178 uint offset
= (((size_t)clicked
- (size_t)topleft
) / (blitter
->GetScreenDepth() / 8)) % bp
.pitch
;
1179 if (offset
< (uint
)bp
.width
) {
1180 include(_newgrf_debug_sprite_picker
.sprites
, sprite_id
);
1185 BlitterFactory::GetCurrentBlitter()->Draw(&bp
, mode
, zoom
);
1188 static void GfxMainBlitterViewport(const Sprite
*sprite
, int x
, int y
, BlitterMode mode
, const SubSprite
*sub
, SpriteID sprite_id
)
1190 GfxBlitter
<ZOOM_LVL_BASE
, false>(sprite
, x
, y
, mode
, sub
, sprite_id
, _cur_dpi
->zoom
);
1193 static void GfxMainBlitter(const Sprite
*sprite
, int x
, int y
, BlitterMode mode
, const SubSprite
*sub
, SpriteID sprite_id
, ZoomLevel zoom
)
1195 GfxBlitter
<1, true>(sprite
, x
, y
, mode
, sub
, sprite_id
, zoom
);
1198 void DoPaletteAnimations();
1200 void GfxInitPalettes()
1202 std::lock_guard
<std::recursive_mutex
> lock(_palette_mutex
);
1203 memcpy(&_cur_palette
, &_palette
, sizeof(_cur_palette
));
1204 DoPaletteAnimations();
1208 * Copy the current palette if the palette was updated.
1209 * Used by video-driver to get a current up-to-date version of the palette,
1210 * to avoid two threads accessing the same piece of memory (with a good chance
1211 * one is already updating the palette while the other is drawing based on it).
1212 * @param local_palette The location to copy the palette to.
1213 * @param force_copy Whether to ignore if there is an update for the palette.
1214 * @return True iff a copy was done.
1216 bool CopyPalette(Palette
&local_palette
, bool force_copy
)
1218 std::lock_guard
<std::recursive_mutex
> lock(_palette_mutex
);
1220 if (!force_copy
&& _cur_palette
.count_dirty
== 0) return false;
1222 local_palette
= _cur_palette
;
1223 _cur_palette
.count_dirty
= 0;
1226 local_palette
.first_dirty
= 0;
1227 local_palette
.count_dirty
= 256;
1233 #define EXTR(p, q) (((uint16)(palette_animation_counter * (p)) * (q)) >> 16)
1234 #define EXTR2(p, q) (((uint16)(~palette_animation_counter * (p)) * (q)) >> 16)
1236 void DoPaletteAnimations()
1238 std::lock_guard
<std::recursive_mutex
> lock(_palette_mutex
);
1240 /* Animation counter for the palette animation. */
1241 static int palette_animation_counter
= 0;
1242 palette_animation_counter
+= 8;
1244 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1246 const ExtraPaletteValues
*ev
= &_extra_palette_values
;
1247 Colour old_val
[PALETTE_ANIM_SIZE
];
1248 const uint old_tc
= palette_animation_counter
;
1252 if (blitter
!= nullptr && blitter
->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE
) {
1253 palette_animation_counter
= 0;
1256 Colour
*palette_pos
= &_cur_palette
.palette
[PALETTE_ANIM_START
]; // Points to where animations are taking place on the palette
1257 /* Makes a copy of the current animation palette in old_val,
1258 * so the work on the current palette could be compared, see if there has been any changes */
1259 memcpy(old_val
, palette_pos
, sizeof(old_val
));
1261 /* Fizzy Drink bubbles animation */
1262 s
= ev
->fizzy_drink
;
1263 j
= EXTR2(512, EPV_CYCLES_FIZZY_DRINK
);
1264 for (i
= 0; i
!= EPV_CYCLES_FIZZY_DRINK
; i
++) {
1265 *palette_pos
++ = s
[j
];
1267 if (j
== EPV_CYCLES_FIZZY_DRINK
) j
= 0;
1270 /* Oil refinery fire animation */
1271 s
= ev
->oil_refinery
;
1272 j
= EXTR2(512, EPV_CYCLES_OIL_REFINERY
);
1273 for (i
= 0; i
!= EPV_CYCLES_OIL_REFINERY
; i
++) {
1274 *palette_pos
++ = s
[j
];
1276 if (j
== EPV_CYCLES_OIL_REFINERY
) j
= 0;
1279 /* Radio tower blinking */
1281 byte i
= (palette_animation_counter
>> 1) & 0x7F;
1286 } else if (i
< 0x4A || i
>= 0x75) {
1299 } else if (i
< 0x4A || i
>= 0x75) {
1310 /* Handle lighthouse and stadium animation */
1312 j
= EXTR(256, EPV_CYCLES_LIGHTHOUSE
);
1313 for (i
= 0; i
!= EPV_CYCLES_LIGHTHOUSE
; i
++) {
1314 *palette_pos
++ = s
[j
];
1316 if (j
== EPV_CYCLES_LIGHTHOUSE
) j
= 0;
1319 /* Dark blue water */
1320 s
= (_settings_game
.game_creation
.landscape
== LT_TOYLAND
) ? ev
->dark_water_toyland
: ev
->dark_water
;
1321 j
= EXTR(320, EPV_CYCLES_DARK_WATER
);
1322 for (i
= 0; i
!= EPV_CYCLES_DARK_WATER
; i
++) {
1323 *palette_pos
++ = s
[j
];
1325 if (j
== EPV_CYCLES_DARK_WATER
) j
= 0;
1328 /* Glittery water */
1329 s
= (_settings_game
.game_creation
.landscape
== LT_TOYLAND
) ? ev
->glitter_water_toyland
: ev
->glitter_water
;
1330 j
= EXTR(128, EPV_CYCLES_GLITTER_WATER
);
1331 for (i
= 0; i
!= EPV_CYCLES_GLITTER_WATER
/ 3; i
++) {
1332 *palette_pos
++ = s
[j
];
1334 if (j
>= EPV_CYCLES_GLITTER_WATER
) j
-= EPV_CYCLES_GLITTER_WATER
;
1337 if (blitter
!= nullptr && blitter
->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE
) {
1338 palette_animation_counter
= old_tc
;
1340 if (memcmp(old_val
, &_cur_palette
.palette
[PALETTE_ANIM_START
], sizeof(old_val
)) != 0 && _cur_palette
.count_dirty
== 0) {
1341 /* Did we changed anything on the palette? Seems so. Mark it as dirty */
1342 _cur_palette
.first_dirty
= PALETTE_ANIM_START
;
1343 _cur_palette
.count_dirty
= PALETTE_ANIM_SIZE
;
1349 * Determine a contrasty text colour for a coloured background.
1350 * @param background Background colour.
1351 * @param threshold Background colour brightness threshold below which the background is considered dark and TC_WHITE is returned, range: 0 - 255, default 128.
1352 * @return TC_BLACK or TC_WHITE depending on what gives a better contrast.
1354 TextColour
GetContrastColour(uint8 background
, uint8 threshold
)
1356 Colour c
= _cur_palette
.palette
[background
];
1357 /* Compute brightness according to http://www.w3.org/TR/AERT#color-contrast.
1358 * The following formula computes 1000 * brightness^2, with brightness being in range 0 to 255. */
1359 uint sq1000_brightness
= c
.r
* c
.r
* 299 + c
.g
* c
.g
* 587 + c
.b
* c
.b
* 114;
1360 /* Compare with threshold brightness which defaults to 128 (50%) */
1361 return sq1000_brightness
< ((uint
) threshold
) * ((uint
) threshold
) * 1000 ? TC_WHITE
: TC_BLACK
;
1365 * Initialize _stringwidth_table cache
1366 * @param monospace Whether to load the monospace cache or the normal fonts.
1368 void LoadStringWidthTable(bool monospace
)
1372 for (FontSize fs
= monospace
? FS_MONO
: FS_BEGIN
; fs
< (monospace
? FS_END
: FS_MONO
); fs
++) {
1373 for (uint i
= 0; i
!= 224; i
++) {
1374 _stringwidth_table
[fs
][i
] = GetGlyphWidth(fs
, i
+ 32);
1378 ReInitAllWindows(false);
1382 * Return width of character glyph.
1383 * @param size Font of the character
1384 * @param key Character code glyph
1385 * @return Width of the character glyph
1387 byte
GetCharacterWidth(FontSize size
, WChar key
)
1389 /* Use _stringwidth_table cache if possible */
1390 if (key
>= 32 && key
< 256) return _stringwidth_table
[size
][key
- 32];
1392 return GetGlyphWidth(size
, key
);
1396 * Return the maximum width of single digit.
1397 * @param size Font of the digit
1398 * @return Width of the digit.
1400 byte
GetDigitWidth(FontSize size
)
1403 for (char c
= '0'; c
<= '9'; c
++) {
1404 width
= std::max(GetCharacterWidth(size
, c
), width
);
1410 * Determine the broadest digits for guessing the maximum width of a n-digit number.
1411 * @param[out] front Broadest digit, which is not 0. (Use this digit as first digit for numbers with more than one digit.)
1412 * @param[out] next Broadest digit, including 0. (Use this digit for all digits, except the first one; or for numbers with only one digit.)
1413 * @param size Font of the digit
1415 void GetBroadestDigit(uint
*front
, uint
*next
, FontSize size
)
1418 for (char c
= '9'; c
>= '0'; c
--) {
1419 int w
= GetCharacterWidth(size
, c
);
1423 if (c
!= '0') *front
= c
- '0';
1428 void ScreenSizeChanged()
1430 _dirty_bytes_per_line
= CeilDiv(_screen
.width
, DIRTY_BLOCK_WIDTH
);
1431 _dirty_blocks
= ReallocT
<byte
>(_dirty_blocks
, _dirty_bytes_per_line
* CeilDiv(_screen
.height
, DIRTY_BLOCK_HEIGHT
));
1433 /* check the dirty rect */
1434 if (_invalid_rect
.right
>= _screen
.width
) _invalid_rect
.right
= _screen
.width
;
1435 if (_invalid_rect
.bottom
>= _screen
.height
) _invalid_rect
.bottom
= _screen
.height
;
1437 /* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */
1438 _cursor
.visible
= false;
1441 void UndrawMouseCursor()
1443 /* Don't undraw mouse cursor if it is handled by the video driver. */
1444 if (VideoDriver::GetInstance()->UseSystemCursor()) return;
1446 /* Don't undraw the mouse cursor if the screen is not ready */
1447 if (_screen
.dst_ptr
== nullptr) return;
1449 if (_cursor
.visible
) {
1450 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1451 _cursor
.visible
= false;
1452 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
);
1453 VideoDriver::GetInstance()->MakeDirty(_cursor
.draw_pos
.x
, _cursor
.draw_pos
.y
, _cursor
.draw_size
.x
, _cursor
.draw_size
.y
);
1457 void DrawMouseCursor()
1459 /* Don't draw mouse cursor if it is handled by the video driver. */
1460 if (VideoDriver::GetInstance()->UseSystemCursor()) return;
1462 /* Don't draw the mouse cursor if the screen is not ready */
1463 if (_screen
.dst_ptr
== nullptr) return;
1465 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1467 /* Redraw mouse cursor but only when it's inside the window */
1468 if (!_cursor
.in_window
) return;
1470 /* Don't draw the mouse cursor if it's already drawn */
1471 if (_cursor
.visible
) {
1472 if (!_cursor
.dirty
) return;
1473 UndrawMouseCursor();
1476 /* Determine visible area */
1477 int left
= _cursor
.pos
.x
+ _cursor
.total_offs
.x
;
1478 int width
= _cursor
.total_size
.x
;
1483 if (left
+ width
> _screen
.width
) {
1484 width
= _screen
.width
- left
;
1486 if (width
<= 0) return;
1488 int top
= _cursor
.pos
.y
+ _cursor
.total_offs
.y
;
1489 int height
= _cursor
.total_size
.y
;
1494 if (top
+ height
> _screen
.height
) {
1495 height
= _screen
.height
- top
;
1497 if (height
<= 0) return;
1499 _cursor
.draw_pos
.x
= left
;
1500 _cursor
.draw_pos
.y
= top
;
1501 _cursor
.draw_size
.x
= width
;
1502 _cursor
.draw_size
.y
= height
;
1504 uint8
*buffer
= _cursor_backup
.Allocate(blitter
->BufferSize(_cursor
.draw_size
.x
, _cursor
.draw_size
.y
));
1506 /* Make backup of stuff below cursor */
1507 blitter
->CopyToBuffer(blitter
->MoveTo(_screen
.dst_ptr
, _cursor
.draw_pos
.x
, _cursor
.draw_pos
.y
), buffer
, _cursor
.draw_size
.x
, _cursor
.draw_size
.y
);
1509 /* Draw cursor on screen */
1510 _cur_dpi
= &_screen
;
1511 for (uint i
= 0; i
< _cursor
.sprite_count
; ++i
) {
1512 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
);
1515 VideoDriver::GetInstance()->MakeDirty(_cursor
.draw_pos
.x
, _cursor
.draw_pos
.y
, _cursor
.draw_size
.x
, _cursor
.draw_size
.y
);
1517 _cursor
.visible
= true;
1518 _cursor
.dirty
= false;
1522 * Repaints a specific rectangle of the screen.
1524 * @param left,top,right,bottom The area of the screen that needs repainting
1525 * @pre The rectangle should have been previously marked dirty with \c AddDirtyBlock.
1526 * @see AddDirtyBlock
1527 * @see DrawDirtyBlocks
1531 void RedrawScreenRect(int left
, int top
, int right
, int bottom
)
1533 assert(right
<= _screen
.width
&& bottom
<= _screen
.height
);
1534 if (_cursor
.visible
) {
1535 if (right
> _cursor
.draw_pos
.x
&&
1536 left
< _cursor
.draw_pos
.x
+ _cursor
.draw_size
.x
&&
1537 bottom
> _cursor
.draw_pos
.y
&&
1538 top
< _cursor
.draw_pos
.y
+ _cursor
.draw_size
.y
) {
1539 UndrawMouseCursor();
1543 if (_networking
) NetworkUndrawChatMessage();
1545 DrawOverlappedWindowForAll(left
, top
, right
, bottom
);
1547 VideoDriver::GetInstance()->MakeDirty(left
, top
, right
- left
, bottom
- top
);
1551 * Repaints the rectangle blocks which are marked as 'dirty'.
1553 * @see AddDirtyBlock
1557 void DrawDirtyBlocks()
1559 byte
*b
= _dirty_blocks
;
1560 const int w
= Align(_screen
.width
, DIRTY_BLOCK_WIDTH
);
1561 const int h
= Align(_screen
.height
, DIRTY_BLOCK_HEIGHT
);
1572 int right
= x
+ DIRTY_BLOCK_WIDTH
;
1577 /* First try coalescing downwards */
1580 p
+= _dirty_bytes_per_line
;
1581 bottom
+= DIRTY_BLOCK_HEIGHT
;
1582 } while (bottom
!= h
&& *p
!= 0);
1584 /* Try coalescing to the right too. */
1585 h2
= (bottom
- y
) / DIRTY_BLOCK_HEIGHT
;
1589 while (right
!= w
) {
1592 /* Check if a full line of dirty flags is set. */
1594 if (!*p2
) goto no_more_coalesc
;
1595 p2
+= _dirty_bytes_per_line
;
1598 /* Wohoo, can combine it one step to the right!
1599 * Do that, and clear the bits. */
1600 right
+= DIRTY_BLOCK_WIDTH
;
1606 p2
+= _dirty_bytes_per_line
;
1614 if (left
< _invalid_rect
.left
) left
= _invalid_rect
.left
;
1615 if (top
< _invalid_rect
.top
) top
= _invalid_rect
.top
;
1616 if (right
> _invalid_rect
.right
) right
= _invalid_rect
.right
;
1617 if (bottom
> _invalid_rect
.bottom
) bottom
= _invalid_rect
.bottom
;
1619 if (left
< right
&& top
< bottom
) {
1620 RedrawScreenRect(left
, top
, right
, bottom
);
1624 } while (b
++, (x
+= DIRTY_BLOCK_WIDTH
) != w
);
1625 } while (b
+= -(int)(w
/ DIRTY_BLOCK_WIDTH
) + _dirty_bytes_per_line
, (y
+= DIRTY_BLOCK_HEIGHT
) != h
);
1627 ++_dirty_block_colour
;
1628 _invalid_rect
.left
= w
;
1629 _invalid_rect
.top
= h
;
1630 _invalid_rect
.right
= 0;
1631 _invalid_rect
.bottom
= 0;
1635 * Extend the internal _invalid_rect rectangle to contain the rectangle
1636 * defined by the given parameters. Note the point (0,0) is top left.
1638 * @param left The left edge of the rectangle
1639 * @param top The top edge of the rectangle
1640 * @param right The right edge of the rectangle
1641 * @param bottom The bottom edge of the rectangle
1642 * @see DrawDirtyBlocks
1646 void AddDirtyBlock(int left
, int top
, int right
, int bottom
)
1652 if (left
< 0) left
= 0;
1653 if (top
< 0) top
= 0;
1654 if (right
> _screen
.width
) right
= _screen
.width
;
1655 if (bottom
> _screen
.height
) bottom
= _screen
.height
;
1657 if (left
>= right
|| top
>= bottom
) return;
1659 if (left
< _invalid_rect
.left
) _invalid_rect
.left
= left
;
1660 if (top
< _invalid_rect
.top
) _invalid_rect
.top
= top
;
1661 if (right
> _invalid_rect
.right
) _invalid_rect
.right
= right
;
1662 if (bottom
> _invalid_rect
.bottom
) _invalid_rect
.bottom
= bottom
;
1664 left
/= DIRTY_BLOCK_WIDTH
;
1665 top
/= DIRTY_BLOCK_HEIGHT
;
1667 b
= _dirty_blocks
+ top
* _dirty_bytes_per_line
+ left
;
1669 width
= ((right
- 1) / DIRTY_BLOCK_WIDTH
) - left
+ 1;
1670 height
= ((bottom
- 1) / DIRTY_BLOCK_HEIGHT
) - top
+ 1;
1672 assert(width
> 0 && height
> 0);
1677 do b
[--i
] = 0xFF; while (i
!= 0);
1679 b
+= _dirty_bytes_per_line
;
1680 } while (--height
!= 0);
1684 * This function mark the whole screen as dirty. This results in repainting
1685 * the whole screen. Use this with care as this function will break the
1686 * idea about marking only parts of the screen as 'dirty'.
1689 void MarkWholeScreenDirty()
1691 AddDirtyBlock(0, 0, _screen
.width
, _screen
.height
);
1695 * Set up a clipping area for only drawing into a certain area. To do this,
1696 * Fill a DrawPixelInfo object with the supplied relative rectangle, backup
1697 * the original (calling) _cur_dpi and assign the just returned DrawPixelInfo
1698 * _cur_dpi. When you are done, give restore _cur_dpi's original value
1699 * @param *n the DrawPixelInfo that will be the clipping rectangle box allowed
1701 * @param left,top,width,height the relative coordinates of the clipping
1702 * rectangle relative to the current _cur_dpi. This will most likely be the
1703 * offset from the calling window coordinates
1704 * @return return false if the requested rectangle is not possible with the
1705 * current dpi pointer. Only continue of the return value is true, or you'll
1706 * get some nasty results
1708 bool FillDrawPixelInfo(DrawPixelInfo
*n
, int left
, int top
, int width
, int height
)
1710 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1711 const DrawPixelInfo
*o
= _cur_dpi
;
1713 n
->zoom
= ZOOM_LVL_NORMAL
;
1718 if ((left
-= o
->left
) < 0) {
1720 if (width
<= 0) return false;
1727 if (width
> o
->width
- left
) {
1728 width
= o
->width
- left
;
1729 if (width
<= 0) return false;
1733 if ((top
-= o
->top
) < 0) {
1735 if (height
<= 0) return false;
1742 n
->dst_ptr
= blitter
->MoveTo(o
->dst_ptr
, left
, top
);
1743 n
->pitch
= o
->pitch
;
1745 if (height
> o
->height
- top
) {
1746 height
= o
->height
- top
;
1747 if (height
<= 0) return false;
1755 * Update cursor dimension.
1756 * Called when changing cursor sprite resp. reloading grfs.
1758 void UpdateCursorSize()
1760 /* Ignore setting any cursor before the sprites are loaded. */
1761 if (GetMaxSpriteID() == 0) return;
1763 static_assert(lengthof(_cursor
.sprite_seq
) == lengthof(_cursor
.sprite_pos
));
1764 assert(_cursor
.sprite_count
<= lengthof(_cursor
.sprite_seq
));
1765 for (uint i
= 0; i
< _cursor
.sprite_count
; ++i
) {
1766 const Sprite
*p
= GetSprite(GB(_cursor
.sprite_seq
[i
].sprite
, 0, SPRITE_WIDTH
), ST_NORMAL
);
1768 offs
.x
= UnScaleGUI(p
->x_offs
) + _cursor
.sprite_pos
[i
].x
;
1769 offs
.y
= UnScaleGUI(p
->y_offs
) + _cursor
.sprite_pos
[i
].y
;
1770 size
.x
= UnScaleGUI(p
->width
);
1771 size
.y
= UnScaleGUI(p
->height
);
1774 _cursor
.total_offs
= offs
;
1775 _cursor
.total_size
= size
;
1777 int right
= std::max(_cursor
.total_offs
.x
+ _cursor
.total_size
.x
, offs
.x
+ size
.x
);
1778 int bottom
= std::max(_cursor
.total_offs
.y
+ _cursor
.total_size
.y
, offs
.y
+ size
.y
);
1779 if (offs
.x
< _cursor
.total_offs
.x
) _cursor
.total_offs
.x
= offs
.x
;
1780 if (offs
.y
< _cursor
.total_offs
.y
) _cursor
.total_offs
.y
= offs
.y
;
1781 _cursor
.total_size
.x
= right
- _cursor
.total_offs
.x
;
1782 _cursor
.total_size
.y
= bottom
- _cursor
.total_offs
.y
;
1786 _cursor
.dirty
= true;
1790 * Switch cursor to different sprite.
1791 * @param cursor Sprite to draw for the cursor.
1792 * @param pal Palette to use for recolouring.
1794 static void SetCursorSprite(CursorID cursor
, PaletteID pal
)
1796 if (_cursor
.sprite_count
== 1 && _cursor
.sprite_seq
[0].sprite
== cursor
&& _cursor
.sprite_seq
[0].pal
== pal
) return;
1798 _cursor
.sprite_count
= 1;
1799 _cursor
.sprite_seq
[0].sprite
= cursor
;
1800 _cursor
.sprite_seq
[0].pal
= pal
;
1801 _cursor
.sprite_pos
[0].x
= 0;
1802 _cursor
.sprite_pos
[0].y
= 0;
1807 static void SwitchAnimatedCursor()
1809 const AnimCursor
*cur
= _cursor
.animate_cur
;
1811 if (cur
== nullptr || cur
->sprite
== AnimCursor::LAST
) cur
= _cursor
.animate_list
;
1813 SetCursorSprite(cur
->sprite
, _cursor
.sprite_seq
[0].pal
);
1815 _cursor
.animate_timeout
= cur
->display_time
;
1816 _cursor
.animate_cur
= cur
+ 1;
1821 if (_cursor
.animate_timeout
!= 0 && --_cursor
.animate_timeout
== 0) {
1822 SwitchAnimatedCursor();
1827 * Set or unset the ZZZ cursor.
1828 * @param busy Whether to show the ZZZ cursor.
1830 void SetMouseCursorBusy(bool busy
)
1833 if (_cursor
.sprite_seq
[0].sprite
== SPR_CURSOR_MOUSE
) SetMouseCursor(SPR_CURSOR_ZZZ
, PAL_NONE
);
1835 if (_cursor
.sprite_seq
[0].sprite
== SPR_CURSOR_ZZZ
) SetMouseCursor(SPR_CURSOR_MOUSE
, PAL_NONE
);
1840 * Assign a single non-animated sprite to the cursor.
1841 * @param sprite Sprite to draw for the cursor.
1842 * @param pal Palette to use for recolouring.
1843 * @see SetAnimatedMouseCursor
1845 void SetMouseCursor(CursorID sprite
, PaletteID pal
)
1847 /* Turn off animation */
1848 _cursor
.animate_timeout
= 0;
1850 SetCursorSprite(sprite
, pal
);
1854 * Assign an animation to the cursor.
1855 * @param table Array of animation states.
1856 * @see SetMouseCursor
1858 void SetAnimatedMouseCursor(const AnimCursor
*table
)
1860 _cursor
.animate_list
= table
;
1861 _cursor
.animate_cur
= nullptr;
1862 _cursor
.sprite_seq
[0].pal
= PAL_NONE
;
1863 SwitchAnimatedCursor();
1867 * Update cursor position on mouse movement for relative modes.
1868 * @param delta_x How much change in the X position.
1869 * @param delta_y How much change in the Y position.
1871 void CursorVars::UpdateCursorPositionRelative(int delta_x
, int delta_y
)
1874 this->delta
.x
= delta_x
;
1875 this->delta
.y
= delta_y
;
1877 int last_position_x
= this->pos
.x
;
1878 int last_position_y
= this->pos
.y
;
1880 this->pos
.x
= Clamp(this->pos
.x
+ delta_x
, 0, _cur_resolution
.width
- 1);
1881 this->pos
.y
= Clamp(this->pos
.y
+ delta_y
, 0, _cur_resolution
.height
- 1);
1883 this->delta
.x
= last_position_x
- this->pos
.x
;
1884 this->delta
.y
= last_position_y
- this->pos
.y
;
1891 * Update cursor position on mouse movement.
1892 * @param x New X position.
1893 * @param y New Y position.
1894 * @param queued_warp True, if the OS queues mouse warps after pending mouse movement events.
1895 * False, if the warp applies instantaneous.
1896 * @return true, if the OS cursor position should be warped back to this->pos.
1898 bool CursorVars::UpdateCursorPosition(int x
, int y
, bool queued_warp
)
1900 /* Detecting relative mouse movement is somewhat tricky.
1901 * - There may be multiple mouse move events in the video driver queue (esp. when OpenTTD lags a bit).
1902 * - When we request warping the mouse position (return true), a mouse move event is appended at the end of the queue.
1904 * So, when this->fix_at is active, we use the following strategy:
1905 * - The first movement triggers the warp to reset the mouse position.
1906 * - Subsequent events have to compute movement relative to the previous event.
1907 * - The relative movement is finished, when we receive the event matching the warp.
1910 if (x
== this->pos
.x
&& y
== this->pos
.y
) {
1911 /* Warp finished. */
1912 this->queued_warp
= false;
1915 this->delta
.x
= x
- (this->queued_warp
? this->last_position
.x
: this->pos
.x
);
1916 this->delta
.y
= y
- (this->queued_warp
? this->last_position
.y
: this->pos
.y
);
1918 this->last_position
.x
= x
;
1919 this->last_position
.y
= y
;
1921 bool need_warp
= false;
1923 if (this->delta
.x
!= 0 || this->delta
.y
!= 0) {
1925 * Note: We also trigger warping again, if there is already a pending warp.
1926 * This makes it more tolerant about the OS or other software in between
1927 * botchering the warp. */
1928 this->queued_warp
= queued_warp
;
1931 } else if (this->pos
.x
!= x
|| this->pos
.y
!= y
) {
1932 this->queued_warp
= false; // Cancel warping, we are no longer confining the position.
1940 bool ChangeResInGame(int width
, int height
)
1942 return (_screen
.width
== width
&& _screen
.height
== height
) || VideoDriver::GetInstance()->ChangeResolution(width
, height
);
1945 bool ToggleFullScreen(bool fs
)
1947 bool result
= VideoDriver::GetInstance()->ToggleFullscreen(fs
);
1948 if (_fullscreen
!= fs
&& _resolutions
.empty()) {
1949 Debug(driver
, 0, "Could not find a suitable fullscreen resolution");
1954 void SortResolutions()
1956 std::sort(_resolutions
.begin(), _resolutions
.end());
1960 * Resolve GUI zoom level, if auto-suggestion is requested.
1962 void UpdateGUIZoom()
1964 /* Determine real GUI zoom to use. */
1965 if (_gui_zoom_cfg
== ZOOM_LVL_CFG_AUTO
) {
1966 _gui_zoom
= static_cast<ZoomLevel
>(Clamp(VideoDriver::GetInstance()->GetSuggestedUIZoom(), _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
1968 /* Ensure the gui_zoom is clamped between min/max. Change the
1969 * _gui_zoom_cfg if it isn't, as this is used to visually show the
1970 * selection in the Game Options. */
1971 _gui_zoom_cfg
= Clamp(_gui_zoom_cfg
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
);
1972 _gui_zoom
= static_cast<ZoomLevel
>(_gui_zoom_cfg
);
1975 /* Determine real font zoom to use. */
1976 if (_font_zoom_cfg
== ZOOM_LVL_CFG_AUTO
) {
1977 _font_zoom
= static_cast<ZoomLevel
>(VideoDriver::GetInstance()->GetSuggestedUIZoom());
1979 _font_zoom
= static_cast<ZoomLevel
>(_font_zoom_cfg
);
1983 void ChangeGameSpeed(bool enable_fast_forward
)
1985 if (enable_fast_forward
) {
1986 _game_speed
= _settings_client
.gui
.fast_forward_speed_limit
;