# gfx.cpp

Go to the documentation of this file.

    00001 /* $Id$ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * 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.
    00006  * 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.
    00007  * 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/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include "gfx_func.h"
    00014 #include "fontcache.h"
    00015 #include "progress.h"
    00016 #include "zoom_func.h"
    00017 #include "blitter/factory.hpp"
    00018 #include "video/video_driver.hpp"
    00019 #include "strings_func.h"
    00020 #include "settings_type.h"
    00021 #include "network/network.h"
    00022 #include "network/network_func.h"
    00023 #include "window_func.h"
    00024 #include "newgrf_debug.h"
    00025
    00026 #include "table/palettes.h"
    00027 #include "table/sprites.h"
    00028 #include "table/control_codes.h"
    00029
    00030 byte _dirkeys;
    00031 bool _fullscreen;
    00032 CursorVars _cursor;
    00033 bool _ctrl_pressed;
    00034 bool _shift_pressed;
    00035 byte _fast_forward;
    00036 bool _left_button_down;
    00037 bool _left_button_clicked;
    00038 bool _right_button_down;
    00039 bool _right_button_clicked;
    00040 DrawPixelInfo _screen;
    00041 bool _screen_disable_anim = false;
    00042 bool _exit_game;
    00043 GameMode _game_mode;
    00044 SwitchMode _switch_mode;
    00045 PauseModeByte _pause_mode;
    00046 Palette _cur_palette;
    00047
    00048 static Dimension _max_char_size[FS_END];
    00049 static int _max_char_height;
    00050 static int _max_char_width;
    00051 static byte _stringwidth_table[FS_END][224];
    00052 DrawPixelInfo *_cur_dpi;
    00053 byte _colour_gradient[COLOUR_END][8];
    00054
    00055 static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL, SpriteID sprite_id = SPR_CURSOR_MOUSE);
    00056 static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL, SpriteID sprite_id = SPR_CURSOR_MOUSE, ZoomLevel zoom = ZOOM_LVL_NORMAL);
    00057
    00062 struct DrawStringParams {
    00063   FontSize fontsize;
    00064   TextColour cur_colour, prev_colour;
    00065
    00066   DrawStringParams(TextColour colour, FontSize fontsize) : fontsize(fontsize), cur_colour(colour), prev_colour(colour) {}
    00067
    00072   inline void SetColour(TextColour c)
    00073   {
    00074     assert(c >=  TC_BLUE && c <= TC_BLACK);
    00075     this->prev_colour = this->cur_colour;
    00076     this->cur_colour = c;
    00077   }
    00078
    00080   inline void SetPreviousColour()
    00081   {
    00082     Swap(this->cur_colour, this->prev_colour);
    00083   }
    00084
    00089   inline void SetFontSize(FontSize f)
    00090   {
    00091     this->fontsize = f;
    00092   }
    00093 };
    00094
    00095 static ReusableBuffer<uint8> _cursor_backup;
    00096
    00104 static Rect _invalid_rect;
    00105 static const byte *_colour_remap_ptr;
    00106 static byte _string_colourremap[3];
    00107
    00108 static const uint DIRTY_BLOCK_HEIGHT   = 8;
    00109 static const uint DIRTY_BLOCK_WIDTH    = 64;
    00110
    00111 static uint _dirty_bytes_per_line = 0;
    00112 static byte *_dirty_blocks = NULL;
    00113 extern uint _dirty_block_colour;
    00114
    00115 void GfxScroll(int left, int top, int width, int height, int xo, int yo)
    00116 {
    00117   Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    00118
    00119   if (xo == 0 && yo == 0) return;
    00120
    00121   if (_cursor.visible) UndrawMouseCursor();
    00122
    00123 #ifdef ENABLE_NETWORK
    00124   if (_networking) NetworkUndrawChatMessage();
    00125 #endif /* ENABLE_NETWORK */
    00126
    00127   blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo);
    00128   /* This part of the screen is now dirty. */
    00129   _video_driver->MakeDirty(left, top, width, height);
    00130 }
    00131
    00132
    00147 void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
    00148 {
    00149   Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    00150   const DrawPixelInfo *dpi = _cur_dpi;
    00151   void *dst;
    00152   const int otop = top;
    00153   const int oleft = left;
    00154
    00155   if (dpi->zoom != ZOOM_LVL_NORMAL) return;
    00156   if (left > right || top > bottom) return;
    00157   if (right < dpi->left || left >= dpi->left + dpi->width) return;
    00158   if (bottom < dpi->top || top >= dpi->top + dpi->height) return;
    00159
    00160   if ( (left -= dpi->left) < 0) left = 0;
    00161   right = right - dpi->left + 1;
    00162   if (right > dpi->width) right = dpi->width;
    00163   right -= left;
    00164   assert(right > 0);
    00165
    00166   if ( (top -= dpi->top) < 0) top = 0;
    00167   bottom = bottom - dpi->top + 1;
    00168   if (bottom > dpi->height) bottom = dpi->height;
    00169   bottom -= top;
    00170   assert(bottom > 0);
    00171
    00172   dst = blitter->MoveTo(dpi->dst_ptr, left, top);
    00173
    00174   switch (mode) {
    00175     default: // FILLRECT_OPAQUE
    00176       blitter->DrawRect(dst, right, bottom, (uint8)colour);
    00177       break;
    00178
    00179     case FILLRECT_RECOLOUR:
    00180       blitter->DrawColourMappingRect(dst, right, bottom, GB(colour, 0, PALETTE_WIDTH));
    00181       break;
    00182
    00183     case FILLRECT_CHECKER: {
    00184       byte bo = (oleft - left + dpi->left + otop - top + dpi->top) & 1;
    00185       do {
    00186         for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, (uint8)colour);
    00187         dst = blitter->MoveTo(dst, 0, 1);
    00188       } while (--bottom > 0);
    00189       break;
    00190     }
    00191   }
    00192 }
    00193
    00207 static inline void GfxDoDrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, uint8 colour, int width)
    00208 {
    00209   Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    00210
    00211   assert(width > 0);
    00212
    00213   if (y2 == y) {
    00214     /* Special case: horizontal line. */
    00215     blitter->DrawLine(video,
    00216         Clamp(x, 0, screen_width), y,
    00217         Clamp(x2, 0, screen_width), y2,
    00218         screen_width, screen_height, colour, width);
    00219     return;
    00220   }
    00221   if (x2 == x) {
    00222     /* Special case: vertical line. */
    00223     blitter->DrawLine(video,
    00224         x, Clamp(y, 0, screen_height),
    00225         x2, Clamp(y2, 0, screen_height),
    00226         screen_width, screen_height, colour, width);
    00227     return;
    00228   }
    00229
    00230   int grade_y = y2 - y;
    00231   int grade_x = x2 - x;
    00232
    00233   /* prevent integer overflows. */
    00234   int margin = 1;
    00235   while (INT_MAX / abs(grade_y) < max(abs(x), abs(screen_width - x))) {
    00236     grade_y /= 2;
    00237     grade_x /= 2;
    00238     margin  *= 2; // account for rounding errors
    00239   }
    00240
    00241   /* If the line is outside the screen on the same side at X positions 0
    00242    * and screen_width, we don't need to draw anything. */
    00243   int offset_0 = y - x * grade_y / grade_x;
    00244   int offset_width = y + (screen_width - x) * grade_y / grade_x;
    00245   if ((offset_0 > screen_height + width / 2 + margin && offset_width > screen_height + width / 2 + margin) ||
    00246       (offset_0 < -width / 2 - margin && offset_width < -width / 2 - margin)) {
    00247     return;
    00248   }
    00249
    00250   /* It is possible to use the line equation to further reduce the amount of
    00251    * work the blitter has to do by shortening the effective line segment.
    00252    * However, in order to get that right and prevent the flickering effects
    00253    * of rounding errors so much additional code has to be run here that in
    00254    * the general case the effect is not noticable. */
    00255
    00256   blitter->DrawLine(video, x, y, x2, y2, screen_width, screen_height, colour, width);
    00257 }
    00258
    00270 static inline bool GfxPreprocessLine(DrawPixelInfo *dpi, int &x, int &y, int &x2, int &y2, int width)
    00271 {
    00272   x -= dpi->left;
    00273   x2 -= dpi->left;
    00274   y -= dpi->top;
    00275   y2 -= dpi->top;
    00276
    00277   /* Check simple clipping */
    00278   if (x + width / 2 < 0           && x2 + width / 2 < 0          ) return false;
    00279   if (y + width / 2 < 0           && y2 + width / 2 < 0          ) return false;
    00280   if (x - width / 2 > dpi->width  && x2 - width / 2 > dpi->width ) return false;
    00281   if (y - width / 2 > dpi->height && y2 - width / 2 > dpi->height) return false;
    00282   return true;
    00283 }
    00284
    00285 void GfxDrawLine(int x, int y, int x2, int y2, int colour, int width)
    00286 {
    00287   DrawPixelInfo *dpi = _cur_dpi;
    00288   if (GfxPreprocessLine(dpi, x, y, x2, y2, width)) {
    00289     GfxDoDrawLine(dpi->dst_ptr, x, y, x2, y2, dpi->width, dpi->height, colour, width);
    00290   }
    00291 }
    00292
    00293 void GfxDrawLineUnscaled(int x, int y, int x2, int y2, int colour)
    00294 {
    00295   DrawPixelInfo *dpi = _cur_dpi;
    00296   if (GfxPreprocessLine(dpi, x, y, x2, y2, 1)) {
    00297     GfxDoDrawLine(dpi->dst_ptr,
    00298         UnScaleByZoom(x, dpi->zoom), UnScaleByZoom(y, dpi->zoom),
    00299         UnScaleByZoom(x2, dpi->zoom), UnScaleByZoom(y2, dpi->zoom),
    00300         UnScaleByZoom(dpi->width, dpi->zoom), UnScaleByZoom(dpi->height, dpi->zoom), colour, 1);
    00301   }
    00302 }
    00303
    00317 void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3)
    00318 {
    00319   /*           ....
    00320    *         ..    ....
    00321    *       ..          ....
    00322    *     ..                ^
    00323    *   <--__(dx1,dy1)    /(dx2,dy2)
    00324    *   :    --__       /   :
    00325    *   :        --__ /     :
    00326    *   :            *(x,y) :
    00327    *   :            |      :
    00328    *   :            |     ..
    00329    *    ....        |(dx3,dy3)
    00330    *        ....    | ..
    00331    *            ....V.
    00332    */
    00333
    00334   static const byte colour = PC_WHITE;
    00335
    00336   GfxDrawLineUnscaled(x, y, x + dx1, y + dy1, colour);
    00337   GfxDrawLineUnscaled(x, y, x + dx2, y + dy2, colour);
    00338   GfxDrawLineUnscaled(x, y, x + dx3, y + dy3, colour);
    00339
    00340   GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx2, y + dy1 + dy2, colour);
    00341   GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx3, y + dy1 + dy3, colour);
    00342   GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx1, y + dy2 + dy1, colour);
    00343   GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx3, y + dy2 + dy3, colour);
    00344   GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx1, y + dy3 + dy1, colour);
    00345   GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx2, y + dy3 + dy2, colour);
    00346 }
    00347
    00352 static void SetColourRemap(TextColour colour)
    00353 {
    00354   if (colour == TC_INVALID) return;
    00355
    00356   /* Black strings have no shading ever; the shading is black, so it
    00357    * would be invisible at best, but it actually makes it illegible. */
    00358   bool no_shade   = (colour & TC_NO_SHADE) != 0 || colour == TC_BLACK;
    00359   bool raw_colour = (colour & TC_IS_PALETTE_COLOUR) != 0;
    00360   colour &= ~(TC_NO_SHADE | TC_IS_PALETTE_COLOUR);
    00361
    00362   _string_colourremap[1] = raw_colour ? (byte)colour : _string_colourmap[colour];
    00363   _string_colourremap[2] = no_shade ? 0 : 1;
    00364   _colour_remap_ptr = _string_colourremap;
    00365 }
    00366
    00367 #if !defined(WITH_ICU)
    00368 static WChar *HandleBiDiAndArabicShapes(WChar *text) { return text; }
    00369 #else
    00370 #include <unicode/ubidi.h>
    00371 #include <unicode/ushape.h>
    00372 #include <unicode/ustring.h>
    00373
    00403 static WChar *HandleBiDiAndArabicShapes(WChar *buffer)
    00404 {
    00405   UChar input[DRAW_STRING_BUFFER];
    00406   UChar intermediate[DRAW_STRING_BUFFER];
    00407   static WChar output[DRAW_STRING_BUFFER];
    00408
    00409   /* Transform from UTF-32 to internal ICU format of UTF-16. */
    00410   UErrorCode err = U_ZERO_ERROR;
    00411   int32_t length = 0;
    00412   u_strFromUTF32(input, lengthof(input), &length, (UChar32 *)buffer, -1, &err);
    00413   if (U_FAILURE(err)) return buffer;
    00414
    00415   UBiDi *para = ubidi_openSized(length, 0, &err);
    00416   if (para == NULL) return buffer;
    00417
    00418   ubidi_setPara(para, input, length, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, NULL, &err);
    00419   length = ubidi_writeReordered(para, intermediate, lengthof(intermediate), UBIDI_REMOVE_BIDI_CONTROLS, &err);
    00420   length = u_shapeArabic(intermediate, length, input, lengthof(input), U_SHAPE_TEXT_DIRECTION_VISUAL_LTR | U_SHAPE_LETTERS_SHAPE, &err);
    00421   ubidi_close(para);
    00422   if (U_FAILURE(err)) return buffer;
    00423
    00424   /* Transform back to UTF-32. */
    00425   u_strToUTF32((UChar32 *)output, lengthof(output), NULL, input, length, &err);
    00426   if (U_FAILURE(err)) return buffer;
    00427
    00428   /* u_strToUTF32 doesn't add a NUL charcter if the buffer is too small, be safe. */
    00429   output[lengthof(output) - 1] = '\0';
    00430   return output;
    00431 }
    00432 #endif /* WITH_ICU */
    00433
    00434
    00444 static int TruncateString(char *str, int maxw, bool ignore_setxy, FontSize start_fontsize)
    00445 {
    00446   int w = 0;
    00447   FontSize size = start_fontsize;
    00448   int ddd, ddd_w;
    00449
    00450   WChar c;
    00451   char *ddd_pos;
    00452
    00453   ddd_w = ddd = GetCharacterWidth(size, '.') * 3;
    00454
    00455   for (ddd_pos = str; (c = Utf8Consume(const_cast<const char **>(&str))) != '\0'; ) {
    00456     if (IsPrintable(c) && !IsTextDirectionChar(c)) {
    00457       w += GetCharacterWidth(size, c);
    00458
    00459       if (w > maxw) {
    00460         /* string got too big... insert dotdotdot, but make sure we do not
    00461          * print anything beyond the string termination character. */
    00462         for (int i = 0; *ddd_pos != '\0' && i < 3; i++, ddd_pos++) *ddd_pos = '.';
    00463         *ddd_pos = '\0';
    00464         return ddd_w;
    00465       }
    00466     } else {
    00467       if (c == SCC_SETX) {
    00468         if (!ignore_setxy) w = *str;
    00469         str++;
    00470       } else if (c == SCC_SETXY) {
    00471         if (!ignore_setxy) w = *str;
    00472         str += 2;
    00473       } else if (c == SCC_TINYFONT) {
    00474         size = FS_SMALL;
    00475         ddd = GetCharacterWidth(size, '.') * 3;
    00476       } else if (c == SCC_BIGFONT) {
    00477         size = FS_LARGE;
    00478         ddd = GetCharacterWidth(size, '.') * 3;
    00479       } else if (c == '\n') {
    00480         DEBUG(misc, 0, "Drawing string using newlines with DrawString instead of DrawStringMultiLine. Please notify the developers of this: [%s]", str);
    00481       }
    00482     }
    00483
    00484     /* Remember the last position where three dots fit. */
    00485     if (w + ddd < maxw) {
    00486       ddd_w = w + ddd;
    00487       ddd_pos = str;
    00488     }
    00489   }
    00490
    00491   return w;
    00492 }
    00493
    00494 static int ReallyDoDrawString(const WChar *string, int x, int y, DrawStringParams &params, bool parse_string_also_when_clipped = false);
    00495
    00502 static int GetStringWidth(const WChar *str, FontSize start_fontsize)
    00503 {
    00504   FontSize size = start_fontsize;
    00505   int max_width;
    00506   int width;
    00507   WChar c;
    00508
    00509   width = max_width = 0;
    00510   for (;;) {
    00511     c = *str++;
    00512     if (c == 0) break;
    00513     if (IsPrintable(c) && !IsTextDirectionChar(c)) {
    00514       width += GetCharacterWidth(size, c);
    00515     } else {
    00516       switch (c) {
    00517         case SCC_SETX:
    00518         case SCC_SETXY:
    00519           /* At this point there is no SCC_SETX(Y) anymore */
    00520           NOT_REACHED();
    00521           break;
    00522         case SCC_TINYFONT: size = FS_SMALL; break;
    00523         case SCC_BIGFONT:  size = FS_LARGE; break;
    00524         case '\n':
    00525           max_width = max(max_width, width);
    00526           break;
    00527       }
    00528     }
    00529   }
    00530
    00531   return max(max_width, width);
    00532 }
    00533
    00552 static int DrawString(int left, int right, int top, char *str, const char *last, DrawStringParams &params, StringAlignment align, bool underline = false, bool truncate = true)
    00553 {
    00554   /* We need the outer limits of both left/right */
    00555   int min_left = INT32_MAX;
    00556   int max_right = INT32_MIN;
    00557
    00558   int initial_left = left;
    00559   int initial_right = right;
    00560   int initial_top = top;
    00561
    00562   if (truncate) TruncateString(str, right - left + 1, (align & SA_STRIP) == SA_STRIP, params.fontsize);
    00563
    00564   /*
    00565    * To support SETX and SETXY properly with RTL languages we have to
    00566    * calculate the offsets from the right. To do this we need to split
    00567    * the string and draw the parts separated by SETX(Y).
    00568    * So here we split
    00569    */
    00570   static SmallVector<WChar *, 4> setx_offsets;
    00571   setx_offsets.Clear();
    00572
    00573   WChar draw_buffer[DRAW_STRING_BUFFER];
    00574   WChar *p = draw_buffer;
    00575
    00576   *setx_offsets.Append() = p;
    00577
    00578   char *loc = str;
    00579   for (;;) {
    00580     WChar c;
    00581     /* We cannot use Utf8Consume as we need the location of the SETX(Y) */
    00582     size_t len = Utf8Decode(&c, loc);
    00583     *p++ = c;
    00584
    00585     if (c == '\0') break;
    00586     if (p >= lastof(draw_buffer) - 3) {
    00587       /* Make sure we never overflow (even if copying SCC_SETX(Y)). */
    00588       *p = '\0';
    00589       break;
    00590     }
    00591     if (c != SCC_SETX && c != SCC_SETXY) {
    00592       loc += len;
    00593       continue;
    00594     }
    00595
    00596     if (align & SA_STRIP) {
    00597       /* We do not want to keep the SETX(Y)!. It was already copied, so
    00598        * remove it and undo the incrementing of the pointer! */
    00599       *p-- = '\0';
    00600       loc += len + (c == SCC_SETXY ? 2 : 1);
    00601       continue;
    00602     }
    00603
    00604     if ((align & SA_HOR_MASK) != SA_LEFT) {
    00605       DEBUG(grf, 1, "Using SETX and/or SETXY when not aligned to the left. Fixing alignment...");
    00606
    00607       /* For left alignment and change the left so it will roughly be in the
    00608        * middle. This will never cause the string to be completely centered,
    00609        * but once SETX is used you cannot be sure the actual content of the
    00610        * string is centered, so it doesn't really matter. */
    00611       align = SA_LEFT | SA_FORCE;
    00612       initial_left = left = max(left, (left + right - (int)GetStringBoundingBox(str).width) / 2);
    00613     }
    00614
    00615     /* We add the begin of the string, but don't add it twice */
    00616     if (p != draw_buffer) {
    00617       *setx_offsets.Append() = p;
    00618       p[-1] = '\0';
    00619       *p++ = c;
    00620     }
    00621
    00622     /* Skip the SCC_SETX(Y) ... */
    00623     loc += len;
    00624     /* ... copy the x coordinate ... */
    00625     *p++ = *loc++;
    00626     /* ... and finally copy the y coordinate if it exists */
    00627     if (c == SCC_SETXY) *p++ = *loc++;
    00628   }
    00629
    00630   /* In case we have a RTL language we swap the alignment. */
    00631   if (!(align & SA_FORCE) && _current_text_dir == TD_RTL && !(align & SA_STRIP) && (align & SA_HOR_MASK) != SA_HOR_CENTER) align ^= SA_RIGHT;
    00632
    00633   for (WChar **iter = setx_offsets.Begin(); iter != setx_offsets.End(); iter++) {
    00634     WChar *to_draw = *iter;
    00635     int offset = 0;
    00636
    00637     /* Skip the SETX(Y) and set the appropriate offsets. */
    00638     if (*to_draw == SCC_SETX || *to_draw == SCC_SETXY) {
    00639       to_draw++;
    00640       offset = *to_draw++;
    00641       if (*to_draw == SCC_SETXY) top = initial_top + *to_draw++;
    00642     }
    00643
    00644     to_draw = HandleBiDiAndArabicShapes(to_draw);
    00645     int w = GetStringWidth(to_draw, params.fontsize);
    00646
    00647     /* right is the right most position to draw on. In this case we want to do
    00648      * calculations with the width of the string. In comparison right can be
    00649      * seen as lastof(todraw) and width as lengthof(todraw). They differ by 1.
    00650      * So most +1/-1 additions are to move from lengthof to 'indices'.
    00651      */
    00652     switch (align & SA_HOR_MASK) {
    00653       case SA_LEFT:
    00654         /* right + 1 = left + w */
    00655         left = initial_left + offset;
    00656         right = left + w - 1;
    00657         break;
    00658
    00659       case SA_HOR_CENTER:
    00660         left  = RoundDivSU(initial_right + 1 + initial_left - w, 2);
    00661         /* right + 1 = left + w */
    00662         right = left + w - 1;
    00663         break;
    00664
    00665       case SA_RIGHT:
    00666         left = initial_right + 1 - w - offset;
    00667         break;
    00668
    00669       default:
    00670         NOT_REACHED();
    00671     }
    00672
    00673     min_left  = min(left, min_left);
    00674     max_right = max(right, max_right);
    00675
    00676     ReallyDoDrawString(to_draw, left, top, params, !truncate);
    00677     if (underline) {
    00678       GfxFillRect(left, top + FONT_HEIGHT_NORMAL, right, top + FONT_HEIGHT_NORMAL, _string_colourremap[1]);
    00679     }
    00680   }
    00681
    00682   return (align & SA_HOR_MASK) == SA_RIGHT ? min_left : max_right;
    00683 }
    00684
    00699 int DrawString(int left, int right, int top, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
    00700 {
    00701   char buffer[DRAW_STRING_BUFFER];
    00702   strecpy(buffer, str, lastof(buffer));
    00703   DrawStringParams params(colour, fontsize);
    00704   return DrawString(left, right, top, buffer, lastof(buffer), params, align, underline);
    00705 }
    00706
    00721 int DrawString(int left, int right, int top, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
    00722 {
    00723   char buffer[DRAW_STRING_BUFFER];
    00724   GetString(buffer, str, lastof(buffer));
    00725   DrawStringParams params(colour, fontsize);
    00726   return DrawString(left, right, top, buffer, lastof(buffer), params, align, underline);
    00727 }
    00728
    00749 uint32 FormatStringLinebreaks(char *str, const char *last, int maxw, FontSize size)
    00750 {
    00751   int num = 0;
    00752
    00753   assert(maxw > 0);
    00754
    00755   for (;;) {
    00756     /* The character *after* the last space. */
    00757     char *last_space = NULL;
    00758     int w = 0;
    00759
    00760     for (;;) {
    00761       WChar c = Utf8Consume(const_cast<const char **>(&str));
    00762       /* whitespace is where we will insert the line-break */
    00763       if (IsWhitespace(c)) last_space = str;
    00764
    00765       if (IsPrintable(c) && !IsTextDirectionChar(c)) {
    00766         int char_w = GetCharacterWidth(size, c);
    00767         w += char_w;
    00768         if (w > maxw) {
    00769           /* The string is longer than maximum width so we need to decide
    00770            * what to do with it. */
    00771           if (w == char_w) {
    00772             /* The character is wider than allowed width; don't know
    00773              * what to do with this case... bail out! */
    00774             return num + (size << 16);
    00775           }
    00776           if (last_space == NULL) {
    00777             /* No space has been found. Just terminate at our current
    00778              * location. This usually happens for languages that do not
    00779              * require spaces in strings, like Chinese, Japanese and
    00780              * Korean. For other languages terminating mid-word might
    00781              * not be the best, but terminating the whole string instead
    00782              * of continuing the word at the next line is worse. */
    00783             str = Utf8PrevChar(str);
    00784             size_t len = strlen(str);
    00785             char *terminator = str + len;
    00786
    00787             /* The string location + length of the string + 1 for '\0'
    00788              * always fits; otherwise there's no trailing '\0' and it
    00789              * it not a valid string. */
    00790             assert(terminator <= last);
    00791             assert(*terminator == '\0');
    00792
    00793             /* If the string is too long we have to terminate it earlier. */
    00794             if (terminator == last) {
    00795               /* Get the 'begin' of the previous character and make that
    00796                * the terminator of the string; we truncate it 'early'. */
    00797               *Utf8PrevChar(terminator) = '\0';
    00798               len = strlen(str);
    00799             }
    00800             /* Also move the terminator! */
    00801             memmove(str + 1, str, len + 1);
    00802             *str = '\0';
    00803             /* str needs to point to the character *after* the last space */
    00804             str++;
    00805           } else {
    00806             /* A space is found; perfect place to terminate */
    00807             str = last_space;
    00808           }
    00809           break;
    00810         }
    00811       } else {
    00812         switch (c) {
    00813           case '\0': return num + (size << 16);
    00814           case SCC_SETX:  str++; break;
    00815           case SCC_SETXY: str += 2; break;
    00816           case SCC_TINYFONT: size = FS_SMALL; break;
    00817           case SCC_BIGFONT:  size = FS_LARGE; break;
    00818           case '\n': goto end_of_inner_loop;
    00819         }
    00820       }
    00821     }
    00822 end_of_inner_loop:
    00823     /* String didn't fit on line (or a '\n' was encountered), so 'dummy' terminate
    00824      * and increase linecount. We use Utf8PrevChar() as also non 1 char long
    00825      * whitespace separators are supported */
    00826     num++;
    00827     char *s = Utf8PrevChar(str);
    00828     *s++ = '\0';
    00829
    00830     /* In which case (see above) we will shift remainder to left and close the gap */
    00831     if (str - s >= 1) {
    00832       for (; str[-1] != '\0';) *s++ = *str++;
    00833     }
    00834   }
    00835 }
    00836
    00837
    00846 static int GetMultilineStringHeight(const char *src, int num, FontSize start_fontsize)
    00847 {
    00848   int maxy = 0;
    00849   int y = 0;
    00850   int fh = GetCharacterHeight(start_fontsize);
    00851
    00852   for (;;) {
    00853     WChar c = Utf8Consume(&src);
    00854
    00855     switch (c) {
    00856       case 0:            y += fh; if (--num < 0) return maxy; break;
    00857       case '\n':         y += fh;                             break;
    00858       case SCC_SETX:     src++;                               break;
    00859       case SCC_SETXY:    src++; y = (int)*src++;              break;
    00860       case SCC_TINYFONT: fh = GetCharacterHeight(FS_SMALL);   break;
    00861       case SCC_BIGFONT:  fh = GetCharacterHeight(FS_LARGE);   break;
    00862       default:           maxy = max<int>(maxy, y + fh);       break;
    00863     }
    00864   }
    00865 }
    00866
    00867
    00874 int GetStringHeight(StringID str, int maxw)
    00875 {
    00876   char buffer[DRAW_STRING_BUFFER];
    00877
    00878   GetString(buffer, str, lastof(buffer));
    00879
    00880   uint32 tmp = FormatStringLinebreaks(buffer, lastof(buffer), maxw);
    00881
    00882   return GetMultilineStringHeight(buffer, GB(tmp, 0, 16), FS_NORMAL);
    00883 }
    00884
    00891 Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestion)
    00892 {
    00893   Dimension box = {suggestion.width, GetStringHeight(str, suggestion.width)};
    00894   return box;
    00895 }
    00896
    00897
    00904 int GetStringHeight(const char *str, int maxw)
    00905 {
    00906   char buffer[DRAW_STRING_BUFFER];
    00907
    00908   strecpy(buffer, str, lastof(buffer));
    00909
    00910   uint32 tmp = FormatStringLinebreaks(buffer, lastof(buffer), maxw);
    00911
    00912   return GetMultilineStringHeight(buffer, GB(tmp, 0, 16), FS_NORMAL);
    00913 }
    00914
    00921 Dimension GetStringMultiLineBoundingBox(const char *str, const Dimension &suggestion)
    00922 {
    00923   Dimension box = {suggestion.width, GetStringHeight(str, suggestion.width)};
    00924   return box;
    00925 }
    00926
    00943 static int DrawStringMultiLine(int left, int right, int top, int bottom, char *str, const char *last, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
    00944 {
    00945   int maxw = right - left + 1;
    00946   int maxh = bottom - top + 1;
    00947
    00948   /* It makes no sense to even try if it can't be drawn anyway, or
    00949    * do we really want to support fonts of 0 or less pixels high? */
    00950   if (maxh <= 0) return top;
    00951
    00952   uint32 tmp = FormatStringLinebreaks(str, last, maxw);
    00953   int num = GB(tmp, 0, 16) + 1;
    00954
    00955   int mt = GetCharacterHeight((FontSize)GB(tmp, 16, 16));
    00956   int total_height = num * mt;
    00957
    00958   int skip_lines = 0;
    00959   if (total_height > maxh) {
    00960     if (maxh < mt) return top; //  Not enough room for a single line.
    00961     if ((align & SA_VERT_MASK) == SA_BOTTOM) {
    00962       skip_lines = num;
    00963       num = maxh / mt;
    00964       skip_lines -= num;
    00965     } else {
    00966       num = maxh / mt;
    00967     }
    00968     total_height = num * mt;
    00969   }
    00970
    00971   int y;
    00972   switch (align & SA_VERT_MASK) {
    00973     case SA_TOP:
    00974       y = top;
    00975       break;
    00976
    00977     case SA_VERT_CENTER:
    00978       y = RoundDivSU(bottom + top - total_height, 2);
    00979       break;
    00980
    00981     case SA_BOTTOM:
    00982       y = bottom - total_height;
    00983       break;
    00984
    00985     default: NOT_REACHED();
    00986   }
    00987
    00988   const char *src = str;
    00989   DrawStringParams params(colour, fontsize);
    00990   int written_top = bottom; // Uppermost position of rendering a line of text
    00991   for (;;) {
    00992     if (skip_lines == 0) {
    00993       char buf2[DRAW_STRING_BUFFER];
    00994       strecpy(buf2, src, lastof(buf2));
    00995       DrawString(left, right, y, buf2, lastof(buf2), params, align, underline, false);
    00996       if (written_top > y) written_top = y;
    00997       y += mt;
    00998       num--;
    00999     }
    01000
    01001     for (;;) {
    01002       WChar c = Utf8Consume(&src);
    01003       if (c == 0) {
    01004         break;
    01005       } else if (c == SCC_SETX) {
    01006         src++;
    01007       } else if (c == SCC_SETXY) {
    01008         src += 2;
    01009       } else if (skip_lines > 0) {
    01010         /* Skipped drawing, so do additional processing to update params. */
    01011         if (c >= SCC_BLUE && c <= SCC_BLACK) {
    01012           params.SetColour((TextColour)(c - SCC_BLUE));
    01013         } else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour.
    01014           params.SetPreviousColour();
    01015         } else if (c == SCC_TINYFONT) {
    01016           params.SetFontSize(FS_SMALL);
    01017         } else if (c == SCC_BIGFONT) {
    01018           params.SetFontSize(FS_LARGE);
    01019         }
    01020
    01021       }
    01022     }
    01023     if (skip_lines > 0) skip_lines--;
    01024     if (num == 0) return ((align & SA_VERT_MASK) == SA_BOTTOM) ? written_top : y;
    01025   }
    01026 }
    01027
    01043 int DrawStringMultiLine(int left, int right, int top, int bottom, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
    01044 {
    01045   char buffer[DRAW_STRING_BUFFER];
    01046   strecpy(buffer, str, lastof(buffer));
    01047   return DrawStringMultiLine(left, right, top, bottom, buffer, lastof(buffer), colour, align, underline, fontsize);
    01048 }
    01049
    01065 int DrawStringMultiLine(int left, int right, int top, int bottom, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
    01066 {
    01067   char buffer[DRAW_STRING_BUFFER];
    01068   GetString(buffer, str, lastof(buffer));
    01069   return DrawStringMultiLine(left, right, top, bottom, buffer, lastof(buffer), colour, align, underline, fontsize);
    01070 }
    01071
    01082 Dimension GetStringBoundingBox(const char *str, FontSize start_fontsize)
    01083 {
    01084   FontSize size = start_fontsize;
    01085   Dimension br;
    01086   uint max_width;
    01087   WChar c;
    01088
    01089   br.width = br.height = max_width = 0;
    01090   for (;;) {
    01091     c = Utf8Consume(&str);
    01092     if (c == 0) break;
    01093     if (IsPrintable(c) && !IsTextDirectionChar(c)) {
    01094       br.width += GetCharacterWidth(size, c);
    01095     } else {
    01096       switch (c) {
    01097         case SCC_SETX: br.width = max((uint)*str++, br.width); break;
    01098         case SCC_SETXY:
    01099           br.width  = max((uint)*str++, br.width);
    01100           br.height = max((uint)*str++, br.height);
    01101           break;
    01102         case SCC_TINYFONT: size = FS_SMALL; break;
    01103         case SCC_BIGFONT:  size = FS_LARGE; break;
    01104         case '\n':
    01105           br.height += GetCharacterHeight(size);
    01106           if (br.width > max_width) max_width = br.width;
    01107           br.width = 0;
    01108           break;
    01109       }
    01110     }
    01111   }
    01112   br.height += GetCharacterHeight(size);
    01113
    01114   br.width  = max(br.width, max_width);
    01115   return br;
    01116 }
    01117
    01124 Dimension GetStringBoundingBox(StringID strid)
    01125 {
    01126   char buffer[DRAW_STRING_BUFFER];
    01127
    01128   GetString(buffer, strid, lastof(buffer));
    01129   return GetStringBoundingBox(buffer);
    01130 }
    01131
    01139 void DrawCharCentered(WChar c, int x, int y, TextColour colour)
    01140 {
    01141   SetColourRemap(colour);
    01142   GfxMainBlitter(GetGlyph(FS_NORMAL, c), x - GetCharacterWidth(FS_NORMAL, c) / 2, y, BM_COLOUR_REMAP);
    01143 }
    01144
    01162 static int ReallyDoDrawString(const WChar *string, int x, int y, DrawStringParams &params, bool parse_string_also_when_clipped)
    01163 {
    01164   DrawPixelInfo *dpi = _cur_dpi;
    01165   bool draw_shadow = GetDrawGlyphShadow();
    01166   WChar c;
    01167   int xo = x;
    01168
    01169   if (!parse_string_also_when_clipped) {
    01170     /* in "mode multiline", the available space have been verified. Not in regular one.
    01171      * So if the string cannot be drawn, return the original start to say so.*/
    01172     if (x >= dpi->left + dpi->width || y >= dpi->top + dpi->height) return x;
    01173   }
    01174
    01175 switch_colour:;
    01176   SetColourRemap(params.cur_colour);
    01177
    01178 check_bounds:
    01179   if (y + _max_char_height <= dpi->top || dpi->top + dpi->height <= y) {
    01180 skip_char:;
    01181     for (;;) {
    01182       c = *string++;
    01183       if (!IsPrintable(c)) goto skip_cont;
    01184     }
    01185   }
    01186
    01187   for (;;) {
    01188     c = *string++;
    01189 skip_cont:;
    01190     if (c == 0) {
    01191       return x;  // Nothing more to draw, get out. And here is the new x position
    01192     }
    01193     if (IsPrintable(c) && !IsTextDirectionChar(c)) {
    01194       if (x >= dpi->left + dpi->width) goto skip_char;
    01195       if (x + _max_char_width >= dpi->left) {
    01196         const Sprite *glyph = GetGlyph(params.fontsize, c);
    01197         if (draw_shadow && params.fontsize == FS_NORMAL && params.cur_colour != TC_BLACK && !(c >= SCC_SPRITE_START && c <= SCC_SPRITE_END)) {
    01198           SetColourRemap(TC_BLACK);
    01199           GfxMainBlitter(glyph, x + 1, y + 1, BM_COLOUR_REMAP);
    01200           SetColourRemap(params.cur_colour);
    01201         }
    01202         GfxMainBlitter(glyph, x, y, BM_COLOUR_REMAP);
    01203       }
    01204       x += GetCharacterWidth(params.fontsize, c);
    01205     } else if (c == '\n') { // newline = {}
    01206       x = xo;  // We require a new line, so the x coordinate is reset
    01207       y += GetCharacterHeight(params.fontsize);
    01208       goto check_bounds;
    01209     } else if (c >= SCC_BLUE && c <= SCC_BLACK) { // change colour?
    01210       params.SetColour((TextColour)(c - SCC_BLUE));
    01211       goto switch_colour;
    01212     } else if (c == SCC_PREVIOUS_COLOUR) { // revert to the previous colour
    01213       params.SetPreviousColour();
    01214       goto switch_colour;
    01215     } else if (c == SCC_SETX || c == SCC_SETXY) { // {SETX}/{SETXY}
    01216       /* The characters are handled before calling this. */
    01217       NOT_REACHED();
    01218     } else if (c == SCC_TINYFONT) { // {TINYFONT}
    01219       params.SetFontSize(FS_SMALL);
    01220     } else if (c == SCC_BIGFONT) { // {BIGFONT}
    01221       params.SetFontSize(FS_LARGE);
    01222     } else if (!IsTextDirectionChar(c)) {
    01223       DEBUG(misc, 0, "[utf8] unknown string command character %d", c);
    01224     }
    01225   }
    01226 }
    01227
    01235 Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
    01236 {
    01237   const Sprite *sprite = GetSprite(sprid, ST_NORMAL);
    01238
    01239   if (offset != NULL) {
    01240     offset->x = UnScaleByZoom(sprite->x_offs, zoom);
    01241     offset->y = UnScaleByZoom(sprite->y_offs, zoom);
    01242   }
    01243
    01244   Dimension d;
    01245   d.width  = max<int>(0, UnScaleByZoom(sprite->x_offs + sprite->width, zoom));
    01246   d.height = max<int>(0, UnScaleByZoom(sprite->y_offs + sprite->height, zoom));
    01247   return d;
    01248 }
    01249
    01258 void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub)
    01259 {
    01260   SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
    01261   if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
    01262     _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
    01263     GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite);
    01264   } else if (pal != PAL_NONE) {
    01265     _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
    01266     GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_COLOUR_REMAP, sub, real_sprite);
    01267   } else {
    01268     GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite);
    01269   }
    01270 }
    01271
    01281 void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub, ZoomLevel zoom)
    01282 {
    01283   SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
    01284   if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
    01285     _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
    01286     GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite, zoom);
    01287   } else if (pal != PAL_NONE) {
    01288     _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
    01289     GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_COLOUR_REMAP, sub, real_sprite, zoom);
    01290   } else {
    01291     GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite, zoom);
    01292   }
    01293 }
    01294
    01295 static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id)
    01296 {
    01297   const DrawPixelInfo *dpi = _cur_dpi;
    01298   Blitter::BlitterParams bp;
    01299
    01300   /* Amount of pixels to clip from the source sprite */
    01301   int clip_left   = (sub != NULL ? max(0,                   -sprite->x_offs + sub->left * ZOOM_LVL_BASE       ) : 0);
    01302   int clip_top    = (sub != NULL ? max(0,                   -sprite->y_offs + sub->top  * ZOOM_LVL_BASE       ) : 0);
    01303   int clip_right  = (sub != NULL ? max(0, sprite->width  - (-sprite->x_offs + (sub->right + 1)  * ZOOM_LVL_BASE)) : 0);
    01304   int clip_bottom = (sub != NULL ? max(0, sprite->height - (-sprite->y_offs + (sub->bottom + 1) * ZOOM_LVL_BASE)) : 0);
    01305
    01306   if (clip_left + clip_right >= sprite->width) return;
    01307   if (clip_top + clip_bottom >= sprite->height) return;
    01308
    01309   /* Move to the correct offset */
    01310   x += sprite->x_offs;
    01311   y += sprite->y_offs;
    01312
    01313   /* Copy the main data directly from the sprite */
    01314   bp.sprite = sprite->data;
    01315   bp.sprite_width = sprite->width;
    01316   bp.sprite_height = sprite->height;
    01317   bp.width = UnScaleByZoom(sprite->width - clip_left - clip_right, dpi->zoom);
    01318   bp.height = UnScaleByZoom(sprite->height - clip_top - clip_bottom, dpi->zoom);
    01319   bp.top = 0;
    01320   bp.left = 0;
    01321   bp.skip_left = UnScaleByZoomLower(clip_left, dpi->zoom);
    01322   bp.skip_top = UnScaleByZoomLower(clip_top, dpi->zoom);
    01323
    01324   x += ScaleByZoom(bp.skip_left, dpi->zoom);
    01325   y += ScaleByZoom(bp.skip_top, dpi->zoom);
    01326
    01327   bp.dst = dpi->dst_ptr;
    01328   bp.pitch = dpi->pitch;
    01329   bp.remap = _colour_remap_ptr;
    01330
    01331   assert(sprite->width > 0);
    01332   assert(sprite->height > 0);
    01333
    01334   if (bp.width <= 0) return;
    01335   if (bp.height <= 0) return;
    01336
    01337   y -= dpi->top;
    01338   /* Check for top overflow */
    01339   if (y < 0) {
    01340     bp.height -= -UnScaleByZoom(y, dpi->zoom);
    01341     if (bp.height <= 0) return;
    01342     bp.skip_top += -UnScaleByZoom(y, dpi->zoom);
    01343     y = 0;
    01344   } else {
    01345     bp.top = UnScaleByZoom(y, dpi->zoom);
    01346   }
    01347
    01348   /* Check for bottom overflow */
    01349   y += ScaleByZoom(bp.height, dpi->zoom) - dpi->height;
    01350   if (y > 0) {
    01351     bp.height -= UnScaleByZoom(y, dpi->zoom);
    01352     if (bp.height <= 0) return;
    01353   }
    01354
    01355   x -= dpi->left;
    01356   /* Check for left overflow */
    01357   if (x < 0) {
    01358     bp.width -= -UnScaleByZoom(x, dpi->zoom);
    01359     if (bp.width <= 0) return;
    01360     bp.skip_left += -UnScaleByZoom(x, dpi->zoom);
    01361     x = 0;
    01362   } else {
    01363     bp.left = UnScaleByZoom(x, dpi->zoom);
    01364   }
    01365
    01366   /* Check for right overflow */
    01367   x += ScaleByZoom(bp.width, dpi->zoom) - dpi->width;
    01368   if (x > 0) {
    01369     bp.width -= UnScaleByZoom(x, dpi->zoom);
    01370     if (bp.width <= 0) return;
    01371   }
    01372
    01373   assert(bp.skip_left + bp.width <= UnScaleByZoom(sprite->width, dpi->zoom));
    01374   assert(bp.skip_top + bp.height <= UnScaleByZoom(sprite->height, dpi->zoom));
    01375
    01376   /* We do not want to catch the mouse. However we also use that spritenumber for unknown (text) sprites. */
    01377   if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && sprite_id != SPR_CURSOR_MOUSE) {
    01378     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    01379     void *topleft = blitter->MoveTo(bp.dst, bp.left, bp.top);
    01380     void *bottomright = blitter->MoveTo(topleft, bp.width - 1, bp.height - 1);
    01381
    01382     void *clicked = _newgrf_debug_sprite_picker.clicked_pixel;
    01383
    01384     if (topleft <= clicked && clicked <= bottomright) {
    01385       uint offset = (((size_t)clicked - (size_t)topleft) / (blitter->GetScreenDepth() / 8)) % bp.pitch;
    01386       if (offset < (uint)bp.width) {
    01387         _newgrf_debug_sprite_picker.sprites.Include(sprite_id);
    01388       }
    01389     }
    01390   }
    01391
    01392   BlitterFactoryBase::GetCurrentBlitter()->Draw(&bp, mode, dpi->zoom);
    01393 }
    01394
    01395 static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id, ZoomLevel zoom)
    01396 {
    01397   const DrawPixelInfo *dpi = _cur_dpi;
    01398   Blitter::BlitterParams bp;
    01399
    01400   /* Amount of pixels to clip from the source sprite */
    01401   int clip_left   = (sub != NULL ? max(0,                   -sprite->x_offs + sub->left       ) : 0);
    01402   int clip_top    = (sub != NULL ? max(0,                   -sprite->y_offs + sub->top        ) : 0);
    01403   int clip_right  = (sub != NULL ? max(0, sprite->width  - (-sprite->x_offs + sub->right  + 1)) : 0);
    01404   int clip_bottom = (sub != NULL ? max(0, sprite->height - (-sprite->y_offs + sub->bottom + 1)) : 0);
    01405
    01406   if (clip_left + clip_right >= sprite->width) return;
    01407   if (clip_top + clip_bottom >= sprite->height) return;
    01408
    01409   /* Scale it */
    01410   x = ScaleByZoom(x, zoom);
    01411   y = ScaleByZoom(y, zoom);
    01412
    01413   /* Move to the correct offset */
    01414   x += sprite->x_offs;
    01415   y += sprite->y_offs;
    01416
    01417   /* Copy the main data directly from the sprite */
    01418   bp.sprite = sprite->data;
    01419   bp.sprite_width = sprite->width;
    01420   bp.sprite_height = sprite->height;
    01421   bp.width = UnScaleByZoom(sprite->width - clip_left - clip_right, zoom);
    01422   bp.height = UnScaleByZoom(sprite->height - clip_top - clip_bottom, zoom);
    01423   bp.top = 0;
    01424   bp.left = 0;
    01425   bp.skip_left = UnScaleByZoomLower(clip_left, zoom);
    01426   bp.skip_top = UnScaleByZoomLower(clip_top, zoom);
    01427
    01428   x += ScaleByZoom(bp.skip_left, zoom);
    01429   y += ScaleByZoom(bp.skip_top, zoom);
    01430
    01431   bp.dst = dpi->dst_ptr;
    01432   bp.pitch = dpi->pitch;
    01433   bp.remap = _colour_remap_ptr;
    01434
    01435   assert(sprite->width > 0);
    01436   assert(sprite->height > 0);
    01437
    01438   if (bp.width <= 0) return;
    01439   if (bp.height <= 0) return;
    01440
    01441   y -= ScaleByZoom(dpi->top, zoom);
    01442   /* Check for top overflow */
    01443   if (y < 0) {
    01444     bp.height -= -UnScaleByZoom(y, zoom);
    01445     if (bp.height <= 0) return;
    01446     bp.skip_top += -UnScaleByZoom(y, zoom);
    01447     y = 0;
    01448   } else {
    01449     bp.top = UnScaleByZoom(y, zoom);
    01450   }
    01451
    01452   /* Check for bottom overflow */
    01453   y += ScaleByZoom(bp.height - dpi->height, zoom);
    01454   if (y > 0) {
    01455     bp.height -= UnScaleByZoom(y, zoom);
    01456     if (bp.height <= 0) return;
    01457   }
    01458
    01459   x -= ScaleByZoom(dpi->left, zoom);
    01460   /* Check for left overflow */
    01461   if (x < 0) {
    01462     bp.width -= -UnScaleByZoom(x, zoom);
    01463     if (bp.width <= 0) return;
    01464     bp.skip_left += -UnScaleByZoom(x, zoom);
    01465     x = 0;
    01466   } else {
    01467     bp.left = UnScaleByZoom(x, zoom);
    01468   }
    01469
    01470   /* Check for right overflow */
    01471   x += ScaleByZoom(bp.width - dpi->width, zoom);
    01472   if (x > 0) {
    01473     bp.width -= UnScaleByZoom(x, zoom);
    01474     if (bp.width <= 0) return;
    01475   }
    01476
    01477   assert(bp.skip_left + bp.width <= UnScaleByZoom(sprite->width, zoom));
    01478   assert(bp.skip_top + bp.height <= UnScaleByZoom(sprite->height, zoom));
    01479
    01480   /* We do not want to catch the mouse. However we also use that spritenumber for unknown (text) sprites. */
    01481   if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && sprite_id != SPR_CURSOR_MOUSE) {
    01482     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    01483     void *topleft = blitter->MoveTo(bp.dst, bp.left, bp.top);
    01484     void *bottomright = blitter->MoveTo(topleft, bp.width - 1, bp.height - 1);
    01485
    01486     void *clicked = _newgrf_debug_sprite_picker.clicked_pixel;
    01487
    01488     if (topleft <= clicked && clicked <= bottomright) {
    01489       uint offset = (((size_t)clicked - (size_t)topleft) / (blitter->GetScreenDepth() / 8)) % bp.pitch;
    01490       if (offset < (uint)bp.width) {
    01491         _newgrf_debug_sprite_picker.sprites.Include(sprite_id);
    01492       }
    01493     }
    01494   }
    01495
    01496   BlitterFactoryBase::GetCurrentBlitter()->Draw(&bp, mode, zoom);
    01497 }
    01498
    01499 void DoPaletteAnimations();
    01500
    01501 void GfxInitPalettes()
    01502 {
    01503   memcpy(&_cur_palette, &_palette, sizeof(_cur_palette));
    01504   DoPaletteAnimations();
    01505 }
    01506
    01507 #define EXTR(p, q) (((uint16)(palette_animation_counter * (p)) * (q)) >> 16)
    01508 #define EXTR2(p, q) (((uint16)(~palette_animation_counter * (p)) * (q)) >> 16)
    01509
    01510 void DoPaletteAnimations()
    01511 {
    01512   /* Animation counter for the palette animation. */
    01513   static int palette_animation_counter = 0;
    01514   palette_animation_counter += 8;
    01515
    01516   Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    01517   const Colour *s;
    01518   const ExtraPaletteValues *ev = &_extra_palette_values;
    01519   Colour old_val[PALETTE_ANIM_SIZE];
    01520   const uint old_tc = palette_animation_counter;
    01521   uint i;
    01522   uint j;
    01523
    01524   if (blitter != NULL && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
    01525     palette_animation_counter = 0;
    01526   }
    01527
    01528   Colour *palette_pos = &_cur_palette.palette[PALETTE_ANIM_START];  // Points to where animations are taking place on the palette
    01529   /* Makes a copy of the current animation palette in old_val,
    01530    * so the work on the current palette could be compared, see if there has been any changes */
    01531   memcpy(old_val, palette_pos, sizeof(old_val));
    01532
    01533   /* Fizzy Drink bubbles animation */
    01534   s = ev->fizzy_drink;
    01535   j = EXTR2(512, EPV_CYCLES_FIZZY_DRINK);
    01536   for (i = 0; i != EPV_CYCLES_FIZZY_DRINK; i++) {
    01537     *palette_pos++ = s[j];
    01538     j++;
    01539     if (j == EPV_CYCLES_FIZZY_DRINK) j = 0;
    01540   }
    01541
    01542   /* Oil refinery fire animation */
    01543   s = ev->oil_refinery;
    01544   j = EXTR2(512, EPV_CYCLES_OIL_REFINERY);
    01545   for (i = 0; i != EPV_CYCLES_OIL_REFINERY; i++) {
    01546     *palette_pos++ = s[j];
    01547     j++;
    01548     if (j == EPV_CYCLES_OIL_REFINERY) j = 0;
    01549   }
    01550
    01551   /* Radio tower blinking */
    01552   {
    01553     byte i = (palette_animation_counter >> 1) & 0x7F;
    01554     byte v;
    01555
    01556     if (i < 0x3f) {
    01557       v = 255;
    01558     } else if (i < 0x4A || i >= 0x75) {
    01559       v = 128;
    01560     } else {
    01561       v = 20;
    01562     }
    01563     palette_pos->r = v;
    01564     palette_pos->g = 0;
    01565     palette_pos->b = 0;
    01566     palette_pos++;
    01567
    01568     i ^= 0x40;
    01569     if (i < 0x3f) {
    01570       v = 255;
    01571     } else if (i < 0x4A || i >= 0x75) {
    01572       v = 128;
    01573     } else {
    01574       v = 20;
    01575     }
    01576     palette_pos->r = v;
    01577     palette_pos->g = 0;
    01578     palette_pos->b = 0;
    01579     palette_pos++;
    01580   }
    01581
    01582   /* Handle lighthouse and stadium animation */
    01583   s = ev->lighthouse;
    01584   j = EXTR(256, EPV_CYCLES_LIGHTHOUSE);
    01585   for (i = 0; i != EPV_CYCLES_LIGHTHOUSE; i++) {
    01586     *palette_pos++ = s[j];
    01587     j++;
    01588     if (j == EPV_CYCLES_LIGHTHOUSE) j = 0;
    01589   }
    01590
    01591   /* Dark blue water */
    01592   s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->dark_water_toyland : ev->dark_water;
    01593   j = EXTR(320, EPV_CYCLES_DARK_WATER);
    01594   for (i = 0; i != EPV_CYCLES_DARK_WATER; i++) {
    01595     *palette_pos++ = s[j];
    01596     j++;
    01597     if (j == EPV_CYCLES_DARK_WATER) j = 0;
    01598   }
    01599
    01600   /* Glittery water */
    01601   s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->glitter_water_toyland : ev->glitter_water;
    01602   j = EXTR(128, EPV_CYCLES_GLITTER_WATER);
    01603   for (i = 0; i != EPV_CYCLES_GLITTER_WATER / 3; i++) {
    01604     *palette_pos++ = s[j];
    01605     j += 3;
    01606     if (j >= EPV_CYCLES_GLITTER_WATER) j -= EPV_CYCLES_GLITTER_WATER;
    01607   }
    01608
    01609   if (blitter != NULL && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
    01610     palette_animation_counter = old_tc;
    01611   } else {
    01612     if (memcmp(old_val, &_cur_palette.palette[PALETTE_ANIM_START], sizeof(old_val)) != 0 && _cur_palette.count_dirty == 0) {
    01613       /* Did we changed anything on the palette? Seems so.  Mark it as dirty */
    01614       _cur_palette.first_dirty = PALETTE_ANIM_START;
    01615       _cur_palette.count_dirty = PALETTE_ANIM_SIZE;
    01616     }
    01617   }
    01618 }
    01619
    01625 TextColour GetContrastColour(uint8 background)
    01626 {
    01627   Colour c = _cur_palette.palette[background];
    01628   /* Compute brightness according to http://www.w3.org/TR/AERT#color-contrast.
    01629    * The following formula computes 1000 * brightness^2, with brightness being in range 0 to 255. */
    01630   uint sq1000_brightness = c.r * c.r * 299 + c.g * c.g * 587 + c.b * c.b * 114;
    01631   /* Compare with threshold brightness 128 (50%) */
    01632   return sq1000_brightness < 128 * 128 * 1000 ? TC_WHITE : TC_BLACK;
    01633 }
    01634
    01639 void LoadStringWidthTable(bool monospace)
    01640 {
    01641   for (FontSize fs = monospace ? FS_MONO : FS_BEGIN; fs < (monospace ? FS_END : FS_MONO); fs++) {
    01642     _max_char_size[fs].width = 0;
    01643     _max_char_size[fs].height = GetCharacterHeight(fs);
    01644     for (uint i = 0; i != 224; i++) {
    01645       _stringwidth_table[fs][i] = GetGlyphWidth(fs, i + 32);
    01646       _max_char_size[fs].width = max<int>(_max_char_size[fs].width, _stringwidth_table[fs][i]);
    01647     }
    01648
    01649     /* Needed because they need to be 1 more than the widest. */
    01650     _max_char_size[fs].width++;
    01651     _max_char_size[fs].height++;
    01652   }
    01653
    01654   _max_char_width  = 0;
    01655   _max_char_height = 0;
    01656   for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
    01657     _max_char_width = max<int>(_max_char_width, _max_char_size[fs].width);
    01658     _max_char_height = max<int>(_max_char_height, _max_char_size[fs].height);
    01659   }
    01660
    01661   ReInitAllWindows();
    01662 }
    01663
    01670 byte GetCharacterWidth(FontSize size, WChar key)
    01671 {
    01672   /* Use _stringwidth_table cache if possible */
    01673   if (key >= 32 && key < 256) return _stringwidth_table[size][key - 32];
    01674
    01675   return GetGlyphWidth(size, key);
    01676 }
    01677
    01683 byte GetDigitWidth(FontSize size)
    01684 {
    01685   byte width = 0;
    01686   for (char c = '0'; c <= '9'; c++) {
    01687     width = max(GetCharacterWidth(size, c), width);
    01688   }
    01689   return width;
    01690 }
    01691
    01692
    01693 void ScreenSizeChanged()
    01694 {
    01695   _dirty_bytes_per_line = CeilDiv(_screen.width, DIRTY_BLOCK_WIDTH);
    01696   _dirty_blocks = ReallocT<byte>(_dirty_blocks, _dirty_bytes_per_line * CeilDiv(_screen.height, DIRTY_BLOCK_HEIGHT));
    01697
    01698   /* check the dirty rect */
    01699   if (_invalid_rect.right >= _screen.width) _invalid_rect.right = _screen.width;
    01700   if (_invalid_rect.bottom >= _screen.height) _invalid_rect.bottom = _screen.height;
    01701
    01702   /* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */
    01703   _cursor.visible = false;
    01704 }
    01705
    01706 void UndrawMouseCursor()
    01707 {
    01708   /* Don't undraw the mouse cursor if the screen is not ready */
    01709   if (_screen.dst_ptr == NULL) return;
    01710
    01711   if (_cursor.visible) {
    01712     Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    01713     _cursor.visible = false;
    01714     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);
    01715     _video_driver->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
    01716   }
    01717 }
    01718
    01719 void DrawMouseCursor()
    01720 {
    01721 #if defined(WINCE)
    01722   /* Don't ever draw the mouse for WinCE, as we work with a stylus */
    01723   return;
    01724 #endif
    01725
    01726   /* Don't draw the mouse cursor if the screen is not ready */
    01727   if (_screen.dst_ptr == NULL) return;
    01728
    01729   Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    01730   int x;
    01731   int y;
    01732   int w;
    01733   int h;
    01734
    01735   /* Redraw mouse cursor but only when it's inside the window */
    01736   if (!_cursor.in_window) return;
    01737
    01738   /* Don't draw the mouse cursor if it's already drawn */
    01739   if (_cursor.visible) {
    01740     if (!_cursor.dirty) return;
    01741     UndrawMouseCursor();
    01742   }
    01743
    01744   w = _cursor.size.x;
    01745   x = _cursor.pos.x + _cursor.offs.x + _cursor.short_vehicle_offset;
    01746   if (x < 0) {
    01747     w += x;
    01748     x = 0;
    01749   }
    01750   if (w > _screen.width - x) w = _screen.width - x;
    01751   if (w <= 0) return;
    01752   _cursor.draw_pos.x = x;
    01753   _cursor.draw_size.x = w;
    01754
    01755   h = _cursor.size.y;
    01756   y = _cursor.pos.y + _cursor.offs.y;
    01757   if (y < 0) {
    01758     h += y;
    01759     y = 0;
    01760   }
    01761   if (h > _screen.height - y) h = _screen.height - y;
    01762   if (h <= 0) return;
    01763   _cursor.draw_pos.y = y;
    01764   _cursor.draw_size.y = h;
    01765
    01766   uint8 *buffer = _cursor_backup.Allocate(blitter->BufferSize(w, h));
    01767
    01768   /* Make backup of stuff below cursor */
    01769   blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, _cursor.draw_pos.x, _cursor.draw_pos.y), buffer, _cursor.draw_size.x, _cursor.draw_size.y);
    01770
    01771   /* Draw cursor on screen */
    01772   _cur_dpi = &_screen;
    01773   DrawSprite(_cursor.sprite, _cursor.pal, _cursor.pos.x + _cursor.short_vehicle_offset, _cursor.pos.y);
    01774
    01775   _video_driver->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
    01776
    01777   _cursor.visible = true;
    01778   _cursor.dirty = false;
    01779 }
    01780
    01781 void RedrawScreenRect(int left, int top, int right, int bottom)
    01782 {
    01783   assert(right <= _screen.width && bottom <= _screen.height);
    01784   if (_cursor.visible) {
    01785     if (right > _cursor.draw_pos.x &&
    01786         left < _cursor.draw_pos.x + _cursor.draw_size.x &&
    01787         bottom > _cursor.draw_pos.y &&
    01788         top < _cursor.draw_pos.y + _cursor.draw_size.y) {
    01789       UndrawMouseCursor();
    01790     }
    01791   }
    01792
    01793 #ifdef ENABLE_NETWORK
    01794   if (_networking) NetworkUndrawChatMessage();
    01795 #endif /* ENABLE_NETWORK */
    01796
    01797   DrawOverlappedWindowForAll(left, top, right, bottom);
    01798
    01799   _video_driver->MakeDirty(left, top, right - left, bottom - top);
    01800 }
    01801
    01807 void DrawDirtyBlocks()
    01808 {
    01809   byte *b = _dirty_blocks;
    01810   const int w = Align(_screen.width,  DIRTY_BLOCK_WIDTH);
    01811   const int h = Align(_screen.height, DIRTY_BLOCK_HEIGHT);
    01812   int x;
    01813   int y;
    01814
    01815   if (HasModalProgress()) {
    01816     /* We are generating the world, so release our rights to the map and
    01817      * painting while we are waiting a bit. */
    01818     _modal_progress_paint_mutex->EndCritical();
    01819     _modal_progress_work_mutex->EndCritical();
    01820
    01821     /* Wait a while and update _realtime_tick so we are given the rights */
    01822     if (!IsFirstModalProgressLoop()) CSleep(MODAL_PROGRESS_REDRAW_TIMEOUT);
    01823     _realtime_tick += MODAL_PROGRESS_REDRAW_TIMEOUT;
    01824     _modal_progress_paint_mutex->BeginCritical();
    01825     _modal_progress_work_mutex->BeginCritical();
    01826
    01827     /* When we ended with the modal progress, do not draw the blocks.
    01828      * Simply let the next run do so, otherwise we would be loading
    01829      * the new state (and possibly change the blitter) when we hold
    01830      * the drawing lock, which we must not do. */
    01831     if (_switch_mode != SM_NONE && !HasModalProgress()) return;
    01832   }
    01833
    01834   y = 0;
    01835   do {
    01836     x = 0;
    01837     do {
    01838       if (*b != 0) {
    01839         int left;
    01840         int top;
    01841         int right = x + DIRTY_BLOCK_WIDTH;
    01842         int bottom = y;
    01843         byte *p = b;
    01844         int h2;
    01845
    01846         /* First try coalescing downwards */
    01847         do {
    01848           *p = 0;
    01849           p += _dirty_bytes_per_line;
    01850           bottom += DIRTY_BLOCK_HEIGHT;
    01851         } while (bottom != h && *p != 0);
    01852
    01853         /* Try coalescing to the right too. */
    01854         h2 = (bottom - y) / DIRTY_BLOCK_HEIGHT;
    01855         assert(h2 > 0);
    01856         p = b;
    01857
    01858         while (right != w) {
    01859           byte *p2 = ++p;
    01860           int h = h2;
    01861           /* Check if a full line of dirty flags is set. */
    01862           do {
    01863             if (!*p2) goto no_more_coalesc;
    01864             p2 += _dirty_bytes_per_line;
    01865           } while (--h != 0);
    01866
    01867           /* Wohoo, can combine it one step to the right!
    01868            * Do that, and clear the bits. */
    01869           right += DIRTY_BLOCK_WIDTH;
    01870
    01871           h = h2;
    01872           p2 = p;
    01873           do {
    01874             *p2 = 0;
    01875             p2 += _dirty_bytes_per_line;
    01876           } while (--h != 0);
    01877         }
    01878         no_more_coalesc:
    01879
    01880         left = x;
    01881         top = y;
    01882
    01883         if (left   < _invalid_rect.left  ) left   = _invalid_rect.left;
    01884         if (top    < _invalid_rect.top   ) top    = _invalid_rect.top;
    01885         if (right  > _invalid_rect.right ) right  = _invalid_rect.right;
    01886         if (bottom > _invalid_rect.bottom) bottom = _invalid_rect.bottom;
    01887
    01888         if (left < right && top < bottom) {
    01889           RedrawScreenRect(left, top, right, bottom);
    01890         }
    01891
    01892       }
    01893     } while (b++, (x += DIRTY_BLOCK_WIDTH) != w);
    01894   } while (b += -(int)(w / DIRTY_BLOCK_WIDTH) + _dirty_bytes_per_line, (y += DIRTY_BLOCK_HEIGHT) != h);
    01895
    01896   ++_dirty_block_colour;
    01897   _invalid_rect.left = w;
    01898   _invalid_rect.top = h;
    01899   _invalid_rect.right = 0;
    01900   _invalid_rect.bottom = 0;
    01901 }
    01902
    01918 void SetDirtyBlocks(int left, int top, int right, int bottom)
    01919 {
    01920   byte *b;
    01921   int width;
    01922   int height;
    01923
    01924   if (left < 0) left = 0;
    01925   if (top < 0) top = 0;
    01926   if (right > _screen.width) right = _screen.width;
    01927   if (bottom > _screen.height) bottom = _screen.height;
    01928
    01929   if (left >= right || top >= bottom) return;
    01930
    01931   if (left   < _invalid_rect.left  ) _invalid_rect.left   = left;
    01932   if (top    < _invalid_rect.top   ) _invalid_rect.top    = top;
    01933   if (right  > _invalid_rect.right ) _invalid_rect.right  = right;
    01934   if (bottom > _invalid_rect.bottom) _invalid_rect.bottom = bottom;
    01935
    01936   left /= DIRTY_BLOCK_WIDTH;
    01937   top  /= DIRTY_BLOCK_HEIGHT;
    01938
    01939   b = _dirty_blocks + top * _dirty_bytes_per_line + left;
    01940
    01941   width  = ((right  - 1) / DIRTY_BLOCK_WIDTH)  - left + 1;
    01942   height = ((bottom - 1) / DIRTY_BLOCK_HEIGHT) - top  + 1;
    01943
    01944   assert(width > 0 && height > 0);
    01945
    01946   do {
    01947     int i = width;
    01948
    01949     do b[--i] = 0xFF; while (i != 0);
    01950
    01951     b += _dirty_bytes_per_line;
    01952   } while (--height != 0);
    01953 }
    01954
    01961 void MarkWholeScreenDirty()
    01962 {
    01963   SetDirtyBlocks(0, 0, _screen.width, _screen.height);
    01964 }
    01965
    01980 bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
    01981 {
    01982   Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
    01983   const DrawPixelInfo *o = _cur_dpi;
    01984
    01985   n->zoom = ZOOM_LVL_NORMAL;
    01986
    01987   assert(width > 0);
    01988   assert(height > 0);
    01989
    01990   if ((left -= o->left) < 0) {
    01991     width += left;
    01992     if (width <= 0) return false;
    01993     n->left = -left;
    01994     left = 0;
    01995   } else {
    01996     n->left = 0;
    01997   }
    01998
    01999   if (width > o->width - left) {
    02000     width = o->width - left;
    02001     if (width <= 0) return false;
    02002   }
    02003   n->width = width;
    02004
    02005   if ((top -= o->top) < 0) {
    02006     height += top;
    02007     if (height <= 0) return false;
    02008     n->top = -top;
    02009     top = 0;
    02010   } else {
    02011     n->top = 0;
    02012   }
    02013
    02014   n->dst_ptr = blitter->MoveTo(o->dst_ptr, left, top);
    02015   n->pitch = o->pitch;
    02016
    02017   if (height > o->height - top) {
    02018     height = o->height - top;
    02019     if (height <= 0) return false;
    02020   }
    02021   n->height = height;
    02022
    02023   return true;
    02024 }
    02025
    02030 void UpdateCursorSize()
    02031 {
    02032   CursorVars *cv = &_cursor;
    02033   const Sprite *p = GetSprite(GB(cv->sprite, 0, SPRITE_WIDTH), ST_NORMAL);
    02034
    02035   cv->size.y = UnScaleByZoom(p->height, ZOOM_LVL_GUI);
    02036   cv->size.x = UnScaleByZoom(p->width, ZOOM_LVL_GUI);
    02037   cv->offs.x = UnScaleByZoom(p->x_offs, ZOOM_LVL_GUI);
    02038   cv->offs.y = UnScaleByZoom(p->y_offs, ZOOM_LVL_GUI);
    02039
    02040   cv->dirty = true;
    02041 }
    02042
    02048 static void SetCursorSprite(CursorID cursor, PaletteID pal)
    02049 {
    02050   CursorVars *cv = &_cursor;
    02051   if (cv->sprite == cursor) return;
    02052
    02053   cv->sprite = cursor;
    02054   cv->pal    = pal;
    02055   UpdateCursorSize();
    02056
    02057   cv->short_vehicle_offset = 0;
    02058 }
    02059
    02060 static void SwitchAnimatedCursor()
    02061 {
    02062   const AnimCursor *cur = _cursor.animate_cur;
    02063
    02064   if (cur == NULL || cur->sprite == AnimCursor::LAST) cur = _cursor.animate_list;
    02065
    02066   SetCursorSprite(cur->sprite, _cursor.pal);
    02067
    02068   _cursor.animate_timeout = cur->display_time;
    02069   _cursor.animate_cur     = cur + 1;
    02070 }
    02071
    02072 void CursorTick()
    02073 {
    02074   if (_cursor.animate_timeout != 0 && --_cursor.animate_timeout == 0) {
    02075     SwitchAnimatedCursor();
    02076   }
    02077 }
    02078
    02085 void SetMouseCursor(CursorID sprite, PaletteID pal)
    02086 {
    02087   /* Turn off animation */
    02088   _cursor.animate_timeout = 0;
    02089   /* Set cursor */
    02090   SetCursorSprite(sprite, pal);
    02091 }
    02092
    02098 void SetAnimatedMouseCursor(const AnimCursor *table)
    02099 {
    02100   _cursor.animate_list = table;
    02101   _cursor.animate_cur = NULL;
    02102   _cursor.pal = PAL_NONE;
    02103   SwitchAnimatedCursor();
    02104 }
    02105
    02106 bool ChangeResInGame(int width, int height)
    02107 {
    02108   return (_screen.width == width && _screen.height == height) || _video_driver->ChangeResolution(width, height);
    02109 }
    02110
    02111 bool ToggleFullScreen(bool fs)
    02112 {
    02113   bool result = _video_driver->ToggleFullscreen(fs);
    02114   if (_fullscreen != fs && _num_resolutions == 0) {
    02115     DEBUG(driver, 0, "Could not find a suitable fullscreen resolution");
    02116   }
    02117   return result;
    02118 }
    02119
    02120 static int CDECL compare_res(const Dimension *pa, const Dimension *pb)
    02121 {
    02122   int x = pa->width - pb->width;
    02123   if (x != 0) return x;
    02124   return pa->height - pb->height;
    02125 }
    02126
    02127 void SortResolutions(int count)
    02128 {
    02129   QSortT(_resolutions, count, &compare_res);
    02130 }