tron@2186: /* $Id$ */ tron@2186: rubidium@10429: /** @file gfx.cpp Handling of drawing text and other gfx related stuff. */ belugas@6505: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" rubidium@8619: #include "gfx_func.h" tron@1349: #include "spritecache.h" tron@2153: #include "variables.h" peter1138@5108: #include "fontcache.h" truelight@4300: #include "genworld.h" rubidium@5217: #include "debug.h" rubidium@8619: #include "zoom_func.h" truelight@7494: #include "texteff.hpp" truelight@7433: #include "blitter/factory.hpp" peter1138@7666: #include "video/video_driver.hpp" rubidium@8610: #include "strings_func.h" rubidium@8627: #include "core/math_func.hpp" rubidium@8766: #include "settings_type.h" rubidium@10247: #include "core/alloc_func.hpp" skidd13@10969: #include "core/sort_func.hpp" rubidium@10444: #include "landscape_type.h" truelight@0: rubidium@8760: #include "table/palettes.h" rubidium@8760: #include "table/sprites.h" rubidium@8760: #include "table/control_codes.h" rubidium@8760: belugas@6505: byte _dirkeys; ///< 1 = left, 2 = up, 4 = right, 8 = down KUDr@5887: bool _fullscreen; KUDr@5887: CursorVars _cursor; belugas@6505: bool _ctrl_pressed; ///< Is Ctrl pressed? belugas@6505: bool _shift_pressed; ///< Is Shift pressed? KUDr@5887: byte _fast_forward; rubidium@10083: bool _left_button_down; ///< Is left mouse button pressed? rubidium@10083: bool _left_button_clicked; ///< Is left mouse button clicked? rubidium@10083: bool _right_button_down; ///< Is right mouse button pressed? rubidium@10083: bool _right_button_clicked; ///< Is right mouse button clicked? KUDr@5887: DrawPixelInfo _screen; frosch@8745: bool _screen_disable_anim = false; ///< Disable palette animation (important for 32bpp-anim blitter during giant screenshot) KUDr@5887: bool _exit_game; KUDr@5887: bool _networking; ///< are we in networking mode? KUDr@5887: byte _game_mode; peter1138@9166: int8 _pause_game; KUDr@5887: int _pal_first_dirty; truelight@7456: int _pal_count_dirty; KUDr@5887: tron@1991: Colour _cur_palette[256]; peter1138@3798: byte _stringwidth_table[FS_END][224]; rubidium@8619: DrawPixelInfo *_cur_dpi; rubidium@8619: byte _colour_gradient[16][8]; rubidium@8619: bool _use_dos_palette; tron@1991: rubidium@8177: static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL); truelight@0: peter1138@3798: FontSize _cur_fontsize; peter1138@3798: static FontSize _last_fontsize; truelight@7374: static uint8 _cursor_backup[64 * 64 * 4]; rubidium@8041: rubidium@8041: /** rubidium@8041: * The rect for repaint. rubidium@8041: * rubidium@8041: * This rectangle defines the area which should be repaint by the video driver. rubidium@8041: * rubidium@8041: * @ingroup dirty rubidium@8041: */ truelight@0: static Rect _invalid_rect; tron@1357: static const byte *_color_remap_ptr; truelight@0: static byte _string_colorremap[3]; truelight@0: rubidium@10241: enum { rubidium@10241: DIRTY_BLOCK_HEIGHT = 8, rubidium@10241: DIRTY_BLOCK_WIDTH = 64, rubidium@10241: }; rubidium@10247: static uint _dirty_bytes_per_line = 0; rubidium@10247: static byte *_dirty_blocks = NULL; truelight@0: tron@2010: void GfxScroll(int left, int top, int width, int height, int xo, int yo) tron@2010: { truelight@7433: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); truelight@0: tron@2010: if (xo == 0 && yo == 0) return; truelight@0: tron@2010: if (_cursor.visible) UndrawMouseCursor(); rubidium@7950: UndrawChatMessage(); truelight@0: truelight@7447: blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo); belugas@6505: /* This part of the screen is now dirty. */ peter1138@7666: _video_driver->MakeDirty(left, top, width, height); truelight@0: } truelight@0: truelight@0: frosch@11092: /** frosch@11092: * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen. frosch@11092: * frosch@11092: * @pre dpi->zoom == ZOOM_LVL_NORMAL, right >= left, bottom >= top frosch@11092: * @param left Minimum X (inclusive) frosch@11092: * @param top Minimum Y (inclusive) frosch@11092: * @param right Maximum X (inclusive) frosch@11092: * @param bottom Maximum Y (inclusive) frosch@11092: * @param color A 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolor spritenumber (FILLRECT_RECOLOR) frosch@11092: * @param mode frosch@11092: * FILLRECT_OPAQUE: Fill the rectangle with the specified color frosch@11092: * FILLRECT_CHECKER: Like FILLRECT_OPAQUE, but only draw every second pixel (used to grey out things) frosch@11092: * FILLRECT_RECOLOR: Apply a recolor sprite to every pixel in the rectangle currently on screen frosch@11092: */ frosch@11092: void GfxFillRect(int left, int top, int right, int bottom, int color, FillRectMode mode) tron@2010: { truelight@7433: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); truelight@7374: const DrawPixelInfo *dpi = _cur_dpi; truelight@7374: void *dst; tron@332: const int otop = top; tron@332: const int oleft = left; truelight@0: truelight@7120: if (dpi->zoom != ZOOM_LVL_NORMAL) return; tron@2010: if (left > right || top > bottom) return; tron@2010: if (right < dpi->left || left >= dpi->left + dpi->width) return; tron@2010: if (bottom < dpi->top || top >= dpi->top + dpi->height) return; truelight@0: truelight@0: if ( (left -= dpi->left) < 0) left = 0; truelight@0: right = right - dpi->left + 1; tron@2010: if (right > dpi->width) right = dpi->width; truelight@0: right -= left; truelight@0: assert(right > 0); truelight@0: truelight@0: if ( (top -= dpi->top) < 0) top = 0; truelight@0: bottom = bottom - dpi->top + 1; tron@2010: if (bottom > dpi->height) bottom = dpi->height; truelight@0: bottom -= top; truelight@0: assert(bottom > 0); truelight@0: truelight@7433: dst = blitter->MoveTo(dpi->dst_ptr, left, top); truelight@0: frosch@11092: switch (mode) { frosch@11092: default: // FILLRECT_OPAQUE peter1138@7443: blitter->DrawRect(dst, right, bottom, (uint8)color); frosch@11092: break; frosch@11092: frosch@11092: case FILLRECT_RECOLOR: truelight@7433: blitter->DrawColorMappingRect(dst, right, bottom, GB(color, 0, PALETTE_WIDTH)); frosch@11092: break; frosch@11092: frosch@11092: case FILLRECT_CHECKER: { frosch@11092: byte bo = (oleft - left + dpi->left + otop - top + dpi->top) & 1; frosch@11092: do { frosch@11092: for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, (uint8)color); frosch@11092: dst = blitter->MoveTo(dst, 0, 1); frosch@11092: } while (--bottom > 0); frosch@11092: break; truelight@0: } truelight@0: } truelight@0: } truelight@0: truelight@0: void GfxDrawLine(int x, int y, int x2, int y2, int color) truelight@0: { truelight@7444: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); truelight@7444: DrawPixelInfo *dpi = _cur_dpi; truelight@0: truelight@7444: x -= dpi->left; truelight@7444: x2 -= dpi->left; truelight@7444: y -= dpi->top; truelight@7444: y2 -= dpi->top; truelight@0: truelight@7444: /* Check clipping */ truelight@7444: if (x < 0 && x2 < 0) return; truelight@7444: if (y < 0 && y2 < 0) return; truelight@7444: if (x > dpi->width && x2 > dpi->width) return; truelight@7444: if (y > dpi->height && y2 > dpi->height) return; truelight@7444: truelight@7444: blitter->DrawLine(dpi->dst_ptr, x, y, x2, y2, dpi->width, dpi->height, color); truelight@0: } truelight@0: rubidium@8147: void GfxDrawLineUnscaled(int x, int y, int x2, int y2, int color) rubidium@8147: { rubidium@8147: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); rubidium@8147: DrawPixelInfo *dpi = _cur_dpi; rubidium@8147: rubidium@8147: x -= dpi->left; rubidium@8147: x2 -= dpi->left; rubidium@8147: y -= dpi->top; rubidium@8147: y2 -= dpi->top; rubidium@8147: rubidium@8147: /* Check clipping */ rubidium@8147: if (x < 0 && x2 < 0) return; rubidium@8147: if (y < 0 && y2 < 0) return; rubidium@8147: if (x > dpi->width && x2 > dpi->width) return; rubidium@8147: if (y > dpi->height && y2 > dpi->height) return; rubidium@8147: rubidium@8147: blitter->DrawLine(dpi->dst_ptr, UnScaleByZoom(x, dpi->zoom), UnScaleByZoom(y, dpi->zoom), rubidium@8147: UnScaleByZoom(x2, dpi->zoom), UnScaleByZoom(y2, dpi->zoom), rubidium@8147: UnScaleByZoom(dpi->width, dpi->zoom), UnScaleByZoom(dpi->height, dpi->zoom), color); rubidium@8147: } rubidium@8147: rubidium@8139: /** rubidium@8139: * Draws the projection of a parallelepiped. rubidium@8139: * This can be used to draw boxes in world coordinates. rubidium@8139: * rubidium@8139: * @param x Screen X-coordinate of top front corner. rubidium@8139: * @param y Screen Y-coordinate of top front corner. rubidium@8139: * @param dx1 Screen X-length of first edge. rubidium@8139: * @param dy1 Screen Y-length of first edge. rubidium@8139: * @param dx2 Screen X-length of second edge. rubidium@8139: * @param dy2 Screen Y-length of second edge. rubidium@8139: * @param dx3 Screen X-length of third edge. rubidium@8139: * @param dy3 Screen Y-length of third edge. rubidium@8139: */ rubidium@8139: void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3) rubidium@8139: { rubidium@8139: /* .... rubidium@8139: * .. .... rubidium@8139: * .. .... rubidium@8139: * .. ^ rubidium@8139: * <--__(dx1,dy1) /(dx2,dy2) rubidium@8139: * : --__ / : rubidium@8139: * : --__ / : rubidium@8139: * : *(x,y) : rubidium@8139: * : | : rubidium@8139: * : | .. rubidium@8139: * .... |(dx3,dy3) rubidium@8139: * .... | .. rubidium@8139: * ....V. rubidium@8139: */ rubidium@8139: rubidium@8139: static const byte color = 255; rubidium@8139: rubidium@8147: GfxDrawLineUnscaled(x, y, x + dx1, y + dy1, color); rubidium@8147: GfxDrawLineUnscaled(x, y, x + dx2, y + dy2, color); rubidium@8147: GfxDrawLineUnscaled(x, y, x + dx3, y + dy3, color); rubidium@8139: rubidium@8147: GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx2, y + dy1 + dy2, color); rubidium@8147: GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx3, y + dy1 + dy3, color); rubidium@8147: GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx1, y + dy2 + dy1, color); rubidium@8147: GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx3, y + dy2 + dy3, color); rubidium@8147: GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx1, y + dy3 + dy1, color); rubidium@8147: GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx2, y + dy3 + dy2, color); rubidium@8139: } rubidium@8139: peter1138@3798: Darkvater@2097: /** Truncate a given string to a maximum width if neccessary. Darkvater@2097: * If the string is truncated, add three dots ('...') to show this. belugas@6977: * @param *str string that is checked and possibly truncated Darkvater@2097: * @param maxw maximum width in pixels of the string Darkvater@2097: * @return new width of (truncated) string */ Darkvater@2113: static int TruncateString(char *str, int maxw) Darkvater@2097: { Darkvater@2113: int w = 0; peter1138@3798: FontSize size = _cur_fontsize; Darkvater@2097: int ddd, ddd_w; Darkvater@2097: peter1138@5108: WChar c; Darkvater@2097: char *ddd_pos; Darkvater@2097: peter1138@3798: ddd_w = ddd = GetCharacterWidth(size, '.') * 3; Darkvater@2097: peter1138@5108: for (ddd_pos = str; (c = Utf8Consume((const char **)&str)) != '\0'; ) { peter1138@5108: if (IsPrintable(c)) { peter1138@3798: w += GetCharacterWidth(size, c); Darkvater@2097: Darkvater@2097: if (w >= maxw) { rubidium@11142: /* string got too big... insert dotdotdot, but make sure we do not rubidium@11142: * print anything beyond the string termination character. */ rubidium@11142: for (int i = 0; *ddd_pos != '\0' && i < 3; i++, ddd_pos++) *ddd_pos = '.'; rubidium@11142: *ddd_pos = '\0'; Darkvater@2097: return ddd_w; Darkvater@2097: } Darkvater@2097: } else { rubidium@11141: if (c == SCC_SETX) { rubidium@11141: w = *str; rubidium@11141: str++; rubidium@11141: } else if (c == SCC_SETXY) { rubidium@11141: w = *str; rubidium@11141: str += 2; rubidium@11141: } else if (c == SCC_TINYFONT) { peter1138@3798: size = FS_SMALL; peter1138@3798: ddd = GetCharacterWidth(size, '.') * 3; peter1138@5108: } else if (c == SCC_BIGFONT) { peter1138@3798: size = FS_LARGE; peter1138@3798: ddd = GetCharacterWidth(size, '.') * 3; Darkvater@2097: } Darkvater@2097: } Darkvater@2097: belugas@6505: /* Remember the last position where three dots fit. */ Darkvater@2097: if (w + ddd < maxw) { Darkvater@2097: ddd_w = w + ddd; Darkvater@2113: ddd_pos = str; Darkvater@2097: } Darkvater@2097: } Darkvater@2097: Darkvater@2097: return w; Darkvater@2097: } Darkvater@2097: Darkvater@4912: static inline int TruncateStringID(StringID src, char *dest, int maxw, const char* last) Darkvater@2097: { Darkvater@4912: GetString(dest, src, last); Darkvater@2097: return TruncateString(dest, maxw); Darkvater@2097: } truelight@0: truelight@0: /* returns right coordinate */ Darkvater@2097: int DrawString(int x, int y, StringID str, uint16 color) truelight@0: { tron@1336: char buffer[512]; tron@1336: Darkvater@4912: GetString(buffer, str, lastof(buffer)); tron@1336: return DoDrawString(buffer, x, y, color); truelight@0: } truelight@0: Darkvater@2097: int DrawStringTruncated(int x, int y, StringID str, uint16 color, uint maxw) Darkvater@2097: { Darkvater@2097: char buffer[512]; Darkvater@4912: TruncateStringID(str, buffer, maxw, lastof(buffer)); Darkvater@2097: return DoDrawString(buffer, x, y, color); Darkvater@2097: } truelight@0: Darkvater@2097: rubidium@4314: int DrawStringRightAligned(int x, int y, StringID str, uint16 color) truelight@0: { tron@1336: char buffer[512]; rubidium@4314: int w; tron@1336: Darkvater@4912: GetString(buffer, str, lastof(buffer)); Darkvater@4609: w = GetStringBoundingBox(buffer).width; rubidium@4314: DoDrawString(buffer, x - w, y, color); rubidium@4314: rubidium@4314: return w; truelight@0: } truelight@0: Darkvater@2097: void DrawStringRightAlignedTruncated(int x, int y, StringID str, uint16 color, uint maxw) Darkvater@2097: { Darkvater@2097: char buffer[512]; truelight@0: Darkvater@4912: TruncateStringID(str, buffer, maxw, lastof(buffer)); Darkvater@4609: DoDrawString(buffer, x - GetStringBoundingBox(buffer).width, y, color); Darkvater@2097: } Darkvater@2097: rubidium@4314: void DrawStringRightAlignedUnderline(int x, int y, StringID str, uint16 color) rubidium@4314: { rubidium@4314: int w = DrawStringRightAligned(x, y, str, color); rubidium@4314: GfxFillRect(x - w, y + 10, x, y + 10, _string_colorremap[1]); rubidium@4314: } rubidium@4314: Darkvater@2097: Darkvater@2097: int DrawStringCentered(int x, int y, StringID str, uint16 color) truelight@0: { tron@1336: char buffer[512]; truelight@0: int w; truelight@0: Darkvater@4912: GetString(buffer, str, lastof(buffer)); truelight@0: Darkvater@4609: w = GetStringBoundingBox(buffer).width; tron@1336: DoDrawString(buffer, x - w / 2, y, color); truelight@0: truelight@0: return w; truelight@0: } truelight@0: Darkvater@2113: int DrawStringCenteredTruncated(int xl, int xr, int y, StringID str, uint16 color) Darkvater@2097: { Darkvater@2097: char buffer[512]; Darkvater@4912: int w = TruncateStringID(str, buffer, xr - xl, lastof(buffer)); Darkvater@2113: return DoDrawString(buffer, (xl + xr - w) / 2, y, color); Darkvater@2097: } Darkvater@2097: Darkvater@2134: int DoDrawStringCentered(int x, int y, const char *str, uint16 color) Darkvater@2134: { Darkvater@4609: int w = GetStringBoundingBox(str).width; Darkvater@2134: DoDrawString(str, x - w / 2, y, color); Darkvater@2134: return w; Darkvater@2134: } Darkvater@2134: Darkvater@2097: void DrawStringCenterUnderline(int x, int y, StringID str, uint16 color) truelight@0: { truelight@0: int w = DrawStringCentered(x, y, str, color); tron@2010: GfxFillRect(x - (w >> 1), y + 10, x - (w >> 1) + w, y + 10, _string_colorremap[1]); truelight@0: } truelight@0: Darkvater@2113: void DrawStringCenterUnderlineTruncated(int xl, int xr, int y, StringID str, uint16 color) Darkvater@2097: { Darkvater@2113: int w = DrawStringCenteredTruncated(xl, xr, y, str, color); Darkvater@2113: GfxFillRect((xl + xr - w) / 2, y + 10, (xl + xr + w) / 2, y + 10, _string_colorremap[1]); Darkvater@2097: } Darkvater@2097: rubidium@8041: /** rubidium@8041: * 'Correct' a string to a maximum length. Longer strings will be cut into Darkvater@4954: * additional lines at whitespace characters if possible. The string parameter Darkvater@4954: * is modified with terminating characters mid-string which are the belugas@6977: * placeholders for the newlines. Darkvater@4954: * The string WILL be truncated if there was no whitespace for the current Darkvater@4954: * line's maximum width. Darkvater@4954: * belugas@6977: * @note To know if the terminating '\0' is the string end or just a Darkvater@4954: * newline, the returned 'num' value should be consulted. The num'th '\0', Darkvater@4954: * starting with index 0 is the real string end. Darkvater@4954: * Darkvater@4954: * @param str string to check and correct for length restrictions Darkvater@4954: * @param maxw the maximum width the string can have on one line Darkvater@4954: * @return return a 32bit wide number consisting of 2 packed values: Darkvater@4954: * 0 - 15 the number of lines ADDED to the string rubidium@8041: * 16 - 31 the fontsize in which the length calculation was done at rubidium@8041: */ Darkvater@4954: uint32 FormatStringLinebreaks(char *str, int maxw) tron@1095: { Darkvater@4954: FontSize size = _cur_fontsize; truelight@0: int num = 0; Darkvater@4954: Darkvater@4954: assert(maxw > 0); truelight@0: tron@2952: for (;;) { Darkvater@4954: char *last_space = NULL; Darkvater@4954: int w = 0; truelight@0: tron@2952: for (;;) { peter1138@5108: WChar c = Utf8Consume((const char **)&str); Darkvater@4954: /* whitespace is where we will insert the line-break */ Darkvater@6541: if (IsWhitespace(c)) last_space = str; truelight@0: peter1138@5108: if (IsPrintable(c)) { peter1138@3798: w += GetCharacterWidth(size, c); Darkvater@4954: /* string is longer than maximum width so we need to decide what to Darkvater@4954: * do. We can do two things: Darkvater@4954: * 1. If no whitespace was found at all up until now (on this line) then Darkvater@4954: * we will truncate the string and bail out. Darkvater@4954: * 2. In all other cases force a linebreak at the last seen whitespace */ truelight@0: if (w > maxw) { Darkvater@4954: if (last_space == NULL) { Darkvater@6541: *Utf8PrevChar(str) = '\0'; Darkvater@4954: return num + (size << 16); Darkvater@4954: } truelight@0: str = last_space; truelight@0: break; truelight@0: } truelight@0: } else { Darkvater@4954: switch (c) { Darkvater@4954: case '\0': return num + (size << 16); break; peter1138@5108: case SCC_SETX: str++; break; rubidium@6987: case SCC_SETXY: str += 2; break; peter1138@5108: case SCC_TINYFONT: size = FS_SMALL; break; peter1138@5108: case SCC_BIGFONT: size = FS_LARGE; break; peter1138@5108: case '\n': goto end_of_inner_loop; Darkvater@4954: } truelight@0: } truelight@0: } Darkvater@4954: end_of_inner_loop: Darkvater@6541: /* String didn't fit on line (or a '\n' was encountered), so 'dummy' terminate Darkvater@6541: * and increase linecount. We use Utf8PrevChar() as also non 1 char long Darkvater@6541: * whitespace seperators are supported */ truelight@0: num++; Darkvater@6541: char *s = Utf8PrevChar(str); Darkvater@6541: *s++ = '\0'; Darkvater@6541: Darkvater@6541: /* In which case (see above) we will shift remainder to left and close the gap */ Darkvater@6541: if (str - s >= 1) { Darkvater@6541: for (; str[-1] != '\0';) *s++ = *str++; Darkvater@6541: } truelight@0: } truelight@0: } truelight@0: frosch@10662: /** Draw a given string with the centre around the given (x,y) coordinates Darkvater@5579: * @param x Centre the string around this pixel width frosch@10662: * @param y Centre the string around this pixel height Darkvater@5579: * @param str String to draw belugas@6977: * @param maxw Maximum width the string can have before it is wrapped */ tron@2634: void DrawStringMultiCenter(int x, int y, StringID str, int maxw) truelight@0: { tron@1336: char buffer[512]; truelight@0: uint32 tmp; tron@2631: int num, w, mt; tron@1323: const char *src; peter1138@5108: WChar c; truelight@0: Darkvater@4912: GetString(buffer, str, lastof(buffer)); truelight@0: tron@1336: tmp = FormatStringLinebreaks(buffer, maxw); tron@2635: num = GB(tmp, 0, 16); truelight@0: rubidium@5838: mt = GetCharacterHeight((FontSize)GB(tmp, 16, 16)); truelight@0: truelight@0: y -= (mt >> 1) * num; truelight@0: tron@1336: src = buffer; truelight@0: tron@2952: for (;;) { Darkvater@4609: w = GetStringBoundingBox(src).width; rubidium@6987: DoDrawString(src, x - (w >> 1), y, 0xFE); peter1138@3798: _cur_fontsize = _last_fontsize; truelight@0: tron@2952: for (;;) { peter1138@5108: c = Utf8Consume(&src); truelight@0: if (c == 0) { truelight@0: y += mt; truelight@0: if (--num < 0) { peter1138@3798: _cur_fontsize = FS_NORMAL; truelight@0: return; truelight@0: } truelight@0: break; peter1138@5108: } else if (c == SCC_SETX) { truelight@0: src++; peter1138@5108: } else if (c == SCC_SETXY) { rubidium@6987: src += 2; truelight@0: } truelight@0: } truelight@0: } truelight@0: } truelight@0: peter1138@4928: maedhros@6703: uint DrawStringMultiLine(int x, int y, StringID str, int maxw, int maxh) tron@2010: { tron@1336: char buffer[512]; truelight@0: uint32 tmp; tron@2753: int num, mt; peter1138@4928: uint total_height; tron@1323: const char *src; peter1138@5108: WChar c; truelight@0: Darkvater@4912: GetString(buffer, str, lastof(buffer)); truelight@0: tron@1336: tmp = FormatStringLinebreaks(buffer, maxw); tron@2635: num = GB(tmp, 0, 16); tron@2631: rubidium@5838: mt = GetCharacterHeight((FontSize)GB(tmp, 16, 16)); peter1138@4928: total_height = (num + 1) * mt; truelight@0: maedhros@6708: if (maxh != -1 && (int)total_height > maxh) { maedhros@6708: /* Check there's room enough for at least one line. */ maedhros@6708: if (maxh < mt) return 0; maedhros@6708: maedhros@6703: num = maxh / mt - 1; maedhros@6703: total_height = (num + 1) * mt; maedhros@6703: } maedhros@6703: tron@1336: src = buffer; truelight@0: tron@2952: for (;;) { truelight@0: DoDrawString(src, x, y, 0xFE); peter1138@3798: _cur_fontsize = _last_fontsize; truelight@0: tron@2952: for (;;) { peter1138@5108: c = Utf8Consume(&src); truelight@0: if (c == 0) { truelight@0: y += mt; truelight@0: if (--num < 0) { peter1138@3798: _cur_fontsize = FS_NORMAL; peter1138@4928: return total_height; truelight@0: } truelight@0: break; peter1138@5108: } else if (c == SCC_SETX) { truelight@0: src++; peter1138@5108: } else if (c == SCC_SETXY) { rubidium@6987: src += 2; truelight@0: } truelight@0: } truelight@0: } truelight@0: } truelight@0: Darkvater@4609: /** Return the string dimension in pixels. The height and width are returned rubidium@8617: * in a single Dimension value. TINYFONT, BIGFONT modifiers are only Darkvater@4609: * supported as the first character of the string. The returned dimensions Darkvater@4609: * are therefore a rough estimation correct for all the current strings Darkvater@4609: * but not every possible combination Darkvater@4609: * @param str string to calculate pixel-width Darkvater@4609: * @return string width and height in pixels */ rubidium@8617: Dimension GetStringBoundingBox(const char *str) tron@1323: { peter1138@3798: FontSize size = _cur_fontsize; rubidium@8617: Dimension br; Darkvater@4609: int max_width; peter1138@5108: WChar c; Darkvater@4557: Darkvater@4609: br.width = br.height = max_width = 0; peter1138@5108: for (;;) { peter1138@5108: c = Utf8Consume(&str); peter1138@5108: if (c == 0) break; peter1138@5108: if (IsPrintable(c)) { Darkvater@4609: br.width += GetCharacterWidth(size, c); truelight@0: } else { Darkvater@4609: switch (c) { Darkvater@5579: case SCC_SETX: br.width += (byte)*str++; break; peter1138@5108: case SCC_SETXY: Darkvater@5579: br.width += (byte)*str++; Darkvater@5579: br.height += (byte)*str++; Darkvater@4609: break; peter1138@5108: case SCC_TINYFONT: size = FS_SMALL; break; peter1138@5108: case SCC_BIGFONT: size = FS_LARGE; break; peter1138@5108: case '\n': Darkvater@4609: br.height += GetCharacterHeight(size); Darkvater@4610: if (br.width > max_width) max_width = br.width; Darkvater@4610: br.width = 0; Darkvater@4609: break; Darkvater@4609: } truelight@0: } truelight@0: } Darkvater@4609: br.height += GetCharacterHeight(size); Darkvater@4557: Darkvater@4609: br.width = max(br.width, max_width); Darkvater@4609: return br; truelight@0: } truelight@0: rubidium@9233: void DrawCharCentered(WChar c, int x, int y, uint16 real_color) rubidium@9233: { rubidium@9233: FontSize size = FS_NORMAL; rubidium@9233: byte color = real_color & 0xFF; rubidium@9233: uint palette = _use_dos_palette ? 1 : 0; rubidium@9233: int w = GetCharacterWidth(size, c); rubidium@9233: rubidium@9233: _string_colorremap[1] = _string_colormap[palette][color].text; rubidium@9233: _string_colorremap[2] = _string_colormap[palette][color].shadow; rubidium@9233: _color_remap_ptr = _string_colorremap; rubidium@9233: rubidium@9233: GfxMainBlitter(GetGlyph(size, c), x - w / 2, y, BM_COLOUR_REMAP); rubidium@9233: } rubidium@9233: Darkvater@4949: /** Draw a string at the given coordinates with the given colour Darkvater@4949: * @param string the string to draw Darkvater@4949: * @param x offset from left side of the screen, if negative offset from the right side belugas@6977: * @param y offset from top side of the screen, if negative offset from the bottom Darkvater@4949: * @param real_color colour of the string, see _string_colormap in rubidium@8619: * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h Darkvater@4949: * @return the x-coordinates where the drawing has finished. If nothing is drawn Darkvater@4949: * the originally passed x-coordinate is returned */ tron@1323: int DoDrawString(const char *string, int x, int y, uint16 real_color) tron@1323: { tron@4522: DrawPixelInfo *dpi = _cur_dpi; peter1138@3798: FontSize size = _cur_fontsize; peter1138@5108: WChar c; truelight@0: int xo = x, yo = y; truelight@0: rubidium@7918: byte color = real_color & 0xFF; rubidium@7918: byte previous_color = color; truelight@543: truelight@0: if (color != 0xFE) { truelight@0: if (x >= dpi->left + dpi->width || rubidium@6987: x + _screen.width * 2 <= dpi->left || truelight@0: y >= dpi->top + dpi->height || truelight@0: y + _screen.height <= dpi->top) truelight@0: return x; truelight@0: truelight@0: if (color != 0xFF) { truelight@0: switch_color:; dominik@657: if (real_color & IS_PALETTE_COLOR) { truelight@543: _string_colorremap[1] = color; maedhros@6974: _string_colorremap[2] = _use_dos_palette ? 1 : 215; truelight@543: } else { maedhros@6974: uint palette = _use_dos_palette ? 1 : 0; maedhros@6974: _string_colorremap[1] = _string_colormap[palette][color].text; maedhros@6974: _string_colorremap[2] = _string_colormap[palette][color].shadow; truelight@543: } truelight@0: _color_remap_ptr = _string_colorremap; truelight@0: } truelight@0: } truelight@0: truelight@0: check_bounds: truelight@0: if (y + 19 <= dpi->top || dpi->top + dpi->height <= y) { truelight@0: skip_char:; tron@2952: for (;;) { peter1138@5108: c = Utf8Consume(&string); peter1138@5108: if (!IsPrintable(c)) goto skip_cont; truelight@0: } truelight@0: } truelight@0: tron@2952: for (;;) { peter1138@5108: c = Utf8Consume(&string); truelight@0: skip_cont:; truelight@0: if (c == 0) { peter1138@3798: _last_fontsize = size; truelight@0: return x; truelight@0: } peter1138@5108: if (IsPrintable(c)) { truelight@0: if (x >= dpi->left + dpi->width) goto skip_char; truelight@0: if (x + 26 >= dpi->left) { truelight@7348: GfxMainBlitter(GetGlyph(size, c), x, y, BM_COLOUR_REMAP); truelight@0: } peter1138@3798: x += GetCharacterWidth(size, c); peter1138@5108: } else if (c == '\n') { // newline = {} truelight@0: x = xo; peter1138@3798: y += GetCharacterHeight(size); truelight@0: goto check_bounds; peter1138@5108: } else if (c >= SCC_BLUE && c <= SCC_BLACK) { // change color? rubidium@7918: previous_color = color; peter1138@5108: color = (byte)(c - SCC_BLUE); truelight@0: goto switch_color; rubidium@7918: } else if (c == SCC_PREVIOUS_COLOUR) { // revert to the previous color rubidium@7918: Swap(color, previous_color); rubidium@7918: goto switch_color; peter1138@5108: } else if (c == SCC_SETX) { // {SETX} tron@1323: x = xo + (byte)*string++; peter1138@5108: } else if (c == SCC_SETXY) {// {SETXY} tron@1323: x = xo + (byte)*string++; tron@1323: y = yo + (byte)*string++; peter1138@5108: } else if (c == SCC_TINYFONT) { // {TINYFONT} peter1138@3798: size = FS_SMALL; peter1138@5108: } else if (c == SCC_BIGFONT) { // {BIGFONT} peter1138@3798: size = FS_LARGE; truelight@0: } else { Darkvater@5579: DEBUG(misc, 0, "[utf8] unknown string command character %d", c); truelight@0: } truelight@0: } truelight@0: } truelight@0: Darkvater@2097: int DoDrawStringTruncated(const char *str, int x, int y, uint16 color, uint maxw) Darkvater@2097: { Darkvater@2097: char buffer[512]; Darkvater@2097: ttd_strlcpy(buffer, str, sizeof(buffer)); Darkvater@2097: TruncateString(buffer, maxw); Darkvater@2097: return DoDrawString(buffer, x, y, color); Darkvater@2097: } Darkvater@2097: rubidium@8177: void DrawSprite(SpriteID img, SpriteID pal, int x, int y, const SubSprite *sub) tron@2010: { skidd13@8424: if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) { peter1138@5919: _color_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH)) + 1; rubidium@8177: GfxMainBlitter(GetSprite(GB(img, 0, SPRITE_WIDTH)), x, y, BM_TRANSPARENT, sub); peter1138@5919: } else if (pal != PAL_NONE) { peter1138@5919: _color_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH)) + 1; rubidium@8177: GfxMainBlitter(GetSprite(GB(img, 0, SPRITE_WIDTH)), x, y, BM_COLOUR_REMAP, sub); truelight@0: } else { rubidium@8177: GfxMainBlitter(GetSprite(GB(img, 0, SPRITE_WIDTH)), x, y, BM_NORMAL, sub); truelight@0: } truelight@0: } truelight@0: smatz@10948: static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub) truelight@0: { belugas@4171: const DrawPixelInfo *dpi = _cur_dpi; truelight@7348: Blitter::BlitterParams bp; truelight@0: rubidium@8177: /* Amount of pixels to clip from the source sprite */ rubidium@8177: int clip_left = (sub != NULL ? max(0, -sprite->x_offs + sub->left ) : 0); rubidium@8177: int clip_top = (sub != NULL ? max(0, -sprite->y_offs + sub->top ) : 0); rubidium@8177: int clip_right = (sub != NULL ? max(0, sprite->width - (-sprite->x_offs + sub->right + 1)) : 0); rubidium@8177: int clip_bottom = (sub != NULL ? max(0, sprite->height - (-sprite->y_offs + sub->bottom + 1)) : 0); rubidium@8177: rubidium@8177: if (clip_left + clip_right >= sprite->width) return; rubidium@8177: if (clip_top + clip_bottom >= sprite->height) return; rubidium@8177: truelight@7348: /* Move to the correct offset */ tron@1351: x += sprite->x_offs; tron@1351: y += sprite->y_offs; truelight@7348: truelight@7348: /* Copy the main data directly from the sprite */ tron@4518: bp.sprite = sprite->data; truelight@7348: bp.sprite_width = sprite->width; truelight@7348: bp.sprite_height = sprite->height; rubidium@8177: bp.width = UnScaleByZoom(sprite->width - clip_left - clip_right, dpi->zoom); rubidium@8177: bp.height = UnScaleByZoom(sprite->height - clip_top - clip_bottom, dpi->zoom); truelight@7348: bp.top = 0; truelight@7348: bp.left = 0; smatz@10948: bp.skip_left = UnScaleByZoomLower(clip_left, dpi->zoom); smatz@10948: bp.skip_top = UnScaleByZoomLower(clip_top, dpi->zoom); rubidium@8177: rubidium@8177: x += ScaleByZoom(bp.skip_left, dpi->zoom); rubidium@8177: y += ScaleByZoom(bp.skip_top, dpi->zoom); rubidium@8177: truelight@0: bp.dst = dpi->dst_ptr; truelight@0: bp.pitch = dpi->pitch; truelight@7348: bp.remap = _color_remap_ptr; truelight@0: truelight@7348: assert(sprite->width > 0); truelight@7348: assert(sprite->height > 0); truelight@7348: truelight@7348: if (bp.width <= 0) return; truelight@7348: if (bp.height <= 0) return; truelight@7348: truelight@7348: y -= dpi->top; truelight@7348: /* Check for top overflow */ truelight@7348: if (y < 0) { truelight@7348: bp.height -= -UnScaleByZoom(y, dpi->zoom); truelight@7348: if (bp.height <= 0) return; truelight@7348: bp.skip_top += -UnScaleByZoom(y, dpi->zoom); truelight@7348: y = 0; truelight@7348: } else { truelight@7348: bp.top = UnScaleByZoom(y, dpi->zoom); truelight@7300: } truelight@0: truelight@7348: /* Check for bottom overflow */ truelight@7348: y += ScaleByZoom(bp.height, dpi->zoom) - dpi->height; truelight@7348: if (y > 0) { truelight@7348: bp.height -= UnScaleByZoom(y, dpi->zoom); truelight@7300: if (bp.height <= 0) return; truelight@7300: } truelight@7300: truelight@7348: x -= dpi->left; truelight@7348: /* Check for left overflow */ truelight@7348: if (x < 0) { truelight@7348: bp.width -= -UnScaleByZoom(x, dpi->zoom); truelight@7300: if (bp.width <= 0) return; truelight@7348: bp.skip_left += -UnScaleByZoom(x, dpi->zoom); truelight@7300: x = 0; truelight@7348: } else { truelight@7348: bp.left = UnScaleByZoom(x, dpi->zoom); truelight@7300: } truelight@7300: truelight@7348: /* Check for right overflow */ truelight@7348: x += ScaleByZoom(bp.width, dpi->zoom) - dpi->width; truelight@7348: if (x > 0) { truelight@7348: bp.width -= UnScaleByZoom(x, dpi->zoom); truelight@7300: if (bp.width <= 0) return; truelight@7300: } truelight@7300: smatz@10948: assert(bp.skip_left + bp.width <= UnScaleByZoom(sprite->width, dpi->zoom)); smatz@10948: assert(bp.skip_top + bp.height <= UnScaleByZoom(sprite->height, dpi->zoom)); smatz@10948: truelight@7348: BlitterFactoryBase::GetCurrentBlitter()->Draw(&bp, mode, dpi->zoom); truelight@0: } truelight@0: rubidium@6573: void DoPaletteAnimations(); dominik@614: rubidium@6573: void GfxInitPalettes() truelight@0: { tron@1991: memcpy(_cur_palette, _palettes[_use_dos_palette ? 1 : 0], sizeof(_cur_palette)); dominik@614: truelight@7460: DoPaletteAnimations(); truelight@0: _pal_first_dirty = 0; glx@7888: _pal_count_dirty = 256; truelight@0: } truelight@0: truelight@7514: #define EXTR(p, q) (((uint16)(_palette_animation_counter * (p)) * (q)) >> 16) truelight@7514: #define EXTR2(p, q) (((uint16)(~_palette_animation_counter * (p)) * (q)) >> 16) truelight@0: rubidium@6573: void DoPaletteAnimations() truelight@0: { truelight@7456: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); belugas@4171: const Colour *s; belugas@4171: Colour *d; dominik@614: /* Amount of colors to be rotated. dominik@614: * A few more for the DOS palette, because the water colors are dominik@614: * 245-254 for DOS and 217-226 for Windows. */ truelight@0: const ExtraPaletteValues *ev = &_extra_palette_values; tron@2005: int c = _use_dos_palette ? 38 : 28; truelight@7456: Colour old_val[38]; tron@2005: uint i; tron@2005: uint j; truelight@7514: uint old_tc = _palette_animation_counter; truelight@7374: truelight@7456: if (blitter != NULL && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) { truelight@7514: _palette_animation_counter = 0; truelight@7374: } truelight@0: tron@1991: d = &_cur_palette[217]; tron@1991: memcpy(old_val, d, c * sizeof(*old_val)); truelight@0: belugas@6505: /* Dark blue water */ rubidium@10775: s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->ac : ev->a; tron@2005: j = EXTR(320, 5); tron@2005: for (i = 0; i != 5; i++) { tron@1991: *d++ = s[j]; tron@1991: j++; tron@1991: if (j == 5) j = 0; truelight@0: } truelight@0: belugas@6505: /* Glittery water */ rubidium@10775: s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->bc : ev->b; tron@1991: j = EXTR(128, 15); tron@2005: for (i = 0; i != 5; i++) { tron@1991: *d++ = s[j]; tron@1991: j += 3; tron@1991: if (j >= 15) j -= 15; truelight@0: } truelight@0: truelight@0: s = ev->e; tron@1991: j = EXTR2(512, 5); tron@2005: for (i = 0; i != 5; i++) { tron@1991: *d++ = s[j]; tron@1991: j++; tron@1991: if (j == 5) j = 0; truelight@0: } truelight@0: belugas@6505: /* Oil refinery fire animation */ truelight@0: s = ev->oil_ref; tron@1991: j = EXTR2(512, 7); tron@2005: for (i = 0; i != 7; i++) { tron@1991: *d++ = s[j]; tron@1991: j++; tron@1991: if (j == 7) j = 0; truelight@0: } truelight@0: belugas@6505: /* Radio tower blinking */ truelight@0: { truelight@7514: byte i = (_palette_animation_counter >> 1) & 0x7F; tron@2005: byte v; tron@2005: truelight@0: (v = 255, i < 0x3f) || truelight@0: (v = 128, i < 0x4A || i >= 0x75) || truelight@0: (v = 20); tron@1991: d->r = v; tron@1991: d->g = 0; tron@1991: d->b = 0; tron@1991: d++; truelight@0: truelight@0: i ^= 0x40; truelight@0: (v = 255, i < 0x3f) || truelight@0: (v = 128, i < 0x4A || i >= 0x75) || truelight@0: (v = 20); tron@1991: d->r = v; tron@1991: d->g = 0; tron@1991: d->b = 0; tron@1991: d++; truelight@0: } truelight@0: belugas@6505: /* Handle lighthouse and stadium animation */ truelight@0: s = ev->lighthouse; tron@1991: j = EXTR(256, 4); tron@2005: for (i = 0; i != 4; i++) { tron@1991: *d++ = s[j]; tron@1991: j++; tron@1991: if (j == 4) j = 0; truelight@0: } truelight@0: belugas@6505: /* Animate water for old DOS graphics */ tron@2005: if (_use_dos_palette) { belugas@6505: /* Dark blue water DOS */ rubidium@10775: s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->ac : ev->a; tron@2005: j = EXTR(320, 5); tron@2005: for (i = 0; i != 5; i++) { tron@1991: *d++ = s[j]; tron@1991: j++; tron@1991: if (j == 5) j = 0; dominik@614: } tron@915: belugas@6505: /* Glittery water DOS */ rubidium@10775: s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->bc : ev->b; tron@1991: j = EXTR(128, 15); tron@2005: for (i = 0; i != 5; i++) { tron@1991: *d++ = s[j]; tron@1991: j += 3; tron@1991: if (j >= 15) j -= 15; dominik@614: } dominik@614: } dominik@614: truelight@7456: if (blitter != NULL && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) { truelight@7514: _palette_animation_counter = old_tc; truelight@7456: } else { truelight@7456: if (memcmp(old_val, &_cur_palette[217], c * sizeof(*old_val)) != 0) { truelight@7456: _pal_first_dirty = 217; truelight@7456: _pal_count_dirty = c; truelight@7456: } truelight@0: } truelight@0: } truelight@0: truelight@0: rubidium@6573: void LoadStringWidthTable() truelight@0: { tron@2005: uint i; truelight@0: peter1138@3797: /* Normal font */ peter1138@3798: for (i = 0; i != 224; i++) { peter1138@5108: _stringwidth_table[FS_NORMAL][i] = GetGlyphWidth(FS_NORMAL, i + 32); truelight@0: } truelight@0: peter1138@3797: /* Small font */ peter1138@3798: for (i = 0; i != 224; i++) { peter1138@5108: _stringwidth_table[FS_SMALL][i] = GetGlyphWidth(FS_SMALL, i + 32); truelight@0: } truelight@0: peter1138@3797: /* Large font */ peter1138@3798: for (i = 0; i != 224; i++) { peter1138@5108: _stringwidth_table[FS_LARGE][i] = GetGlyphWidth(FS_LARGE, i + 32); truelight@0: } truelight@0: } truelight@0: peter1138@5108: peter1138@5108: byte GetCharacterWidth(FontSize size, WChar key) peter1138@5108: { peter1138@5108: if (key >= 32 && key < 256) return _stringwidth_table[size][key - 32]; peter1138@5108: peter1138@5108: return GetGlyphWidth(size, key); peter1138@5108: } peter1138@5108: peter1138@5108: rubidium@6573: void ScreenSizeChanged() truelight@0: { rubidium@10247: _dirty_bytes_per_line = (_screen.width + DIRTY_BLOCK_WIDTH - 1) / DIRTY_BLOCK_WIDTH; rubidium@10247: _dirty_blocks = ReallocT(_dirty_blocks, _dirty_bytes_per_line * ((_screen.height + DIRTY_BLOCK_HEIGHT - 1) / DIRTY_BLOCK_HEIGHT)); rubidium@10247: belugas@6505: /* check the dirty rect */ truelight@0: if (_invalid_rect.right >= _screen.width) _invalid_rect.right = _screen.width; truelight@0: if (_invalid_rect.bottom >= _screen.height) _invalid_rect.bottom = _screen.height; truelight@0: belugas@6505: /* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */ truelight@0: _cursor.visible = false; truelight@0: } truelight@0: rubidium@6573: void UndrawMouseCursor() truelight@0: { truelight@0: if (_cursor.visible) { truelight@7433: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); truelight@0: _cursor.visible = false; truelight@7481: blitter->CopyFromBuffer(blitter->MoveTo(_screen.dst_ptr, _cursor.draw_pos.x, _cursor.draw_pos.y), _cursor_backup, _cursor.draw_size.x, _cursor.draw_size.y); peter1138@7666: _video_driver->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y); truelight@0: } truelight@0: } truelight@0: rubidium@6573: void DrawMouseCursor() truelight@0: { truelight@7904: #if defined(WINCE) truelight@7904: /* Don't ever draw the mouse for WinCE, as we work with a stylus */ truelight@7904: return; truelight@7904: #endif truelight@7904: truelight@7433: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); tron@2010: int x; tron@2010: int y; tron@2010: int w; tron@2010: int h; truelight@0: Darkvater@3312: /* Redraw mouse cursor but only when it's inside the window */ Darkvater@3312: if (!_cursor.in_window) return; Darkvater@3312: belugas@6505: /* Don't draw the mouse cursor if it's already drawn */ truelight@0: if (_cursor.visible) { tron@2010: if (!_cursor.dirty) return; truelight@0: UndrawMouseCursor(); truelight@0: } truelight@0: truelight@0: w = _cursor.size.x; truelight@0: x = _cursor.pos.x + _cursor.offs.x; tron@2010: if (x < 0) { tron@2010: w += x; tron@2010: x = 0; tron@2010: } tron@2010: if (w > _screen.width - x) w = _screen.width - x; truelight@0: if (w <= 0) return; truelight@0: _cursor.draw_pos.x = x; truelight@0: _cursor.draw_size.x = w; truelight@0: truelight@0: h = _cursor.size.y; truelight@0: y = _cursor.pos.y + _cursor.offs.y; tron@2010: if (y < 0) { tron@2010: h += y; tron@2010: y = 0; tron@2010: } tron@2010: if (h > _screen.height - y) h = _screen.height - y; truelight@0: if (h <= 0) return; truelight@0: _cursor.draw_pos.y = y; truelight@0: _cursor.draw_size.y = h; truelight@0: truelight@7433: assert(blitter->BufferSize(w, h) < (int)sizeof(_cursor_backup)); truelight@0: belugas@6505: /* Make backup of stuff below cursor */ truelight@7481: blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, _cursor.draw_pos.x, _cursor.draw_pos.y), _cursor_backup, _cursor.draw_size.x, _cursor.draw_size.y); truelight@0: belugas@6505: /* Draw cursor on screen */ truelight@0: _cur_dpi = &_screen; peter1138@5919: DrawSprite(_cursor.sprite, _cursor.pal, _cursor.pos.x, _cursor.pos.y); truelight@0: peter1138@7666: _video_driver->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y); truelight@0: truelight@0: _cursor.visible = true; truelight@0: _cursor.dirty = false; truelight@0: } truelight@0: truelight@0: void RedrawScreenRect(int left, int top, int right, int bottom) truelight@0: { truelight@0: assert(right <= _screen.width && bottom <= _screen.height); truelight@0: if (_cursor.visible) { truelight@0: if (right > _cursor.draw_pos.x && truelight@0: left < _cursor.draw_pos.x + _cursor.draw_size.x && truelight@0: bottom > _cursor.draw_pos.y && truelight@0: top < _cursor.draw_pos.y + _cursor.draw_size.y) { truelight@0: UndrawMouseCursor(); truelight@0: } truelight@0: } rubidium@7950: UndrawChatMessage(); truelight@0: truelight@7495: DrawOverlappedWindowForAll(left, top, right, bottom); truelight@0: peter1138@7666: _video_driver->MakeDirty(left, top, right - left, bottom - top); truelight@0: } truelight@0: rubidium@8041: /*! rubidium@8041: * Repaints the rectangle blocks which are marked as 'dirty'. rubidium@8041: * rubidium@8041: * @see SetDirtyBlocks rubidium@8041: */ rubidium@6573: void DrawDirtyBlocks() truelight@0: { truelight@0: byte *b = _dirty_blocks; rubidium@10241: const int w = Align(_screen.width, DIRTY_BLOCK_WIDTH); rubidium@10241: const int h = Align(_screen.height, DIRTY_BLOCK_HEIGHT); tron@2025: int x; tron@2025: int y; truelight@0: truelight@4300: if (IsGeneratingWorld() && !IsGeneratingWorldReadyForPaint()) return; truelight@4300: tron@2025: y = 0; truelight@0: do { tron@2025: x = 0; tron@2025: do { tron@2025: if (*b != 0) { tron@2025: int left; tron@2025: int top; rubidium@10241: int right = x + DIRTY_BLOCK_WIDTH; tron@2025: int bottom = y; tron@2025: byte *p = b; tron@2025: int h2; truelight@0: belugas@6505: /* First try coalescing downwards */ truelight@0: do { tron@2025: *p = 0; rubidium@10247: p += _dirty_bytes_per_line; rubidium@10241: bottom += DIRTY_BLOCK_HEIGHT; tron@2025: } while (bottom != h && *p != 0); truelight@0: belugas@6505: /* Try coalescing to the right too. */ rubidium@10241: h2 = (bottom - y) / DIRTY_BLOCK_HEIGHT; tron@2025: assert(h2 > 0); tron@2025: p = b; truelight@0: tron@2025: while (right != w) { tron@2025: byte *p2 = ++p; tron@2025: int h = h2; belugas@6505: /* Check if a full line of dirty flags is set. */ tron@2025: do { tron@2025: if (!*p2) goto no_more_coalesc; rubidium@10247: p2 += _dirty_bytes_per_line; tron@2025: } while (--h != 0); tron@2025: belugas@6505: /* Wohoo, can combine it one step to the right! belugas@6505: * Do that, and clear the bits. */ rubidium@10241: right += DIRTY_BLOCK_WIDTH; tron@2025: tron@2025: h = h2; tron@2025: p2 = p; tron@2025: do { tron@2025: *p2 = 0; rubidium@10247: p2 += _dirty_bytes_per_line; tron@2025: } while (--h != 0); tron@2025: } tron@2025: no_more_coalesc: tron@2025: tron@2025: left = x; tron@2025: top = y; tron@2025: tron@2025: if (left < _invalid_rect.left ) left = _invalid_rect.left; tron@2025: if (top < _invalid_rect.top ) top = _invalid_rect.top; tron@2025: if (right > _invalid_rect.right ) right = _invalid_rect.right; tron@2025: if (bottom > _invalid_rect.bottom) bottom = _invalid_rect.bottom; tron@2025: tron@2025: if (left < right && top < bottom) { tron@2025: RedrawScreenRect(left, top, right, bottom); tron@2025: } tron@2025: truelight@0: } rubidium@10241: } while (b++, (x += DIRTY_BLOCK_WIDTH) != w); rubidium@10247: } while (b += -(w / DIRTY_BLOCK_WIDTH) + _dirty_bytes_per_line, (y += DIRTY_BLOCK_HEIGHT) != h); truelight@0: truelight@0: _invalid_rect.left = w; truelight@0: _invalid_rect.top = h; truelight@0: _invalid_rect.right = 0; truelight@0: _invalid_rect.bottom = 0; truelight@4300: truelight@4300: /* If we are generating a world, and waiting for a paint run, mark it here truelight@4300: * as done painting, so we can continue generating. */ truelight@4300: if (IsGeneratingWorld() && IsGeneratingWorldReadyForPaint()) { truelight@4300: SetGeneratingWorldPaintStatus(false); truelight@4300: } truelight@0: } truelight@0: rubidium@8041: /*! rubidium@8041: * This function extends the internal _invalid_rect rectangle as it rubidium@8041: * now contains the rectangle defined by the given parameters. Note rubidium@8041: * the point (0,0) is top left. rubidium@8041: * rubidium@8041: * @param left The left edge of the rectangle rubidium@8041: * @param top The top edge of the rectangle rubidium@8041: * @param right The right edge of the rectangle rubidium@8041: * @param bottom The bottm edge of the rectangle rubidium@8041: * @see DrawDirtyBlocks rubidium@8041: * rubidium@8041: * @todo The name of the function should be called like @c AddDirtyBlock as rubidium@8041: * it neither set a dirty rect nor add several dirty rects although rubidium@8041: * the function name is in plural. (Progman) rubidium@8041: */ truelight@0: void SetDirtyBlocks(int left, int top, int right, int bottom) truelight@0: { truelight@0: byte *b; tron@2010: int width; tron@2010: int height; truelight@0: truelight@0: if (left < 0) left = 0; truelight@0: if (top < 0) top = 0; truelight@0: if (right > _screen.width) right = _screen.width; truelight@0: if (bottom > _screen.height) bottom = _screen.height; truelight@0: tron@2010: if (left >= right || top >= bottom) return; truelight@0: tron@2010: if (left < _invalid_rect.left ) _invalid_rect.left = left; tron@2010: if (top < _invalid_rect.top ) _invalid_rect.top = top; tron@2010: if (right > _invalid_rect.right ) _invalid_rect.right = right; tron@2010: if (bottom > _invalid_rect.bottom) _invalid_rect.bottom = bottom; truelight@0: rubidium@10241: left /= DIRTY_BLOCK_WIDTH; rubidium@10241: top /= DIRTY_BLOCK_HEIGHT; truelight@0: rubidium@10247: b = _dirty_blocks + top * _dirty_bytes_per_line + left; truelight@0: rubidium@10241: width = ((right - 1) / DIRTY_BLOCK_WIDTH) - left + 1; rubidium@10241: height = ((bottom - 1) / DIRTY_BLOCK_HEIGHT) - top + 1; truelight@0: truelight@0: assert(width > 0 && height > 0); truelight@0: truelight@0: do { tron@2010: int i = width; tron@2010: truelight@0: do b[--i] = 0xFF; while (i); truelight@0: rubidium@10247: b += _dirty_bytes_per_line; tron@2010: } while (--height != 0); truelight@0: } truelight@0: rubidium@8041: /*! rubidium@8041: * This function mark the whole screen as dirty. This results in repainting rubidium@8041: * the whole screen. Use this with care as this function will break the rubidium@8041: * idea about marking only parts of the screen as 'dirty'. rubidium@8041: */ rubidium@6573: void MarkWholeScreenDirty() truelight@0: { truelight@0: SetDirtyBlocks(0, 0, _screen.width, _screen.height); truelight@0: } truelight@0: Darkvater@4958: /** Set up a clipping area for only drawing into a certain area. To do this, Darkvater@4958: * Fill a DrawPixelInfo object with the supplied relative rectangle, backup Darkvater@4958: * the original (calling) _cur_dpi and assign the just returned DrawPixelInfo Darkvater@4958: * _cur_dpi. When you are done, give restore _cur_dpi's original value Darkvater@4958: * @param *n the DrawPixelInfo that will be the clipping rectangle box allowed Darkvater@4958: * for drawing Darkvater@4958: * @param left,top,width,height the relative coordinates of the clipping Darkvater@4958: * rectangle relative to the current _cur_dpi. This will most likely be the Darkvater@4958: * offset from the calling window coordinates Darkvater@4958: * @return return false if the requested rectangle is not possible with the Darkvater@4958: * current dpi pointer. Only continue of the return value is true, or you'll Darkvater@4958: * get some nasty results */ tron@4429: bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height) truelight@0: { truelight@7433: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); tron@4429: const DrawPixelInfo *o = _cur_dpi; truelight@0: truelight@7120: n->zoom = ZOOM_LVL_NORMAL; truelight@0: truelight@0: assert(width > 0); truelight@0: assert(height > 0); truelight@0: truelight@0: if ((left -= o->left) < 0) { tron@2010: width += left; tron@4525: if (width <= 0) return false; truelight@0: n->left = -left; truelight@0: left = 0; tron@4429: } else { tron@4429: n->left = 0; truelight@0: } truelight@0: tron@4429: if (width > o->width - left) { tron@4429: width = o->width - left; tron@4525: if (width <= 0) return false; truelight@0: } truelight@0: n->width = width; truelight@0: truelight@0: if ((top -= o->top) < 0) { tron@2010: height += top; tron@4525: if (height <= 0) return false; truelight@0: n->top = -top; truelight@0: top = 0; tron@4429: } else { tron@4429: n->top = 0; truelight@0: } truelight@0: truelight@7433: n->dst_ptr = blitter->MoveTo(o->dst_ptr, left, top); truelight@7374: n->pitch = o->pitch; truelight@0: tron@4429: if (height > o->height - top) { tron@4429: height = o->height - top; tron@4525: if (height <= 0) return false; truelight@0: } truelight@0: n->height = height; truelight@0: truelight@0: return true; truelight@0: } truelight@0: peter1138@5919: static void SetCursorSprite(SpriteID cursor, SpriteID pal) truelight@0: { truelight@0: CursorVars *cv = &_cursor; tron@1348: const Sprite *p; truelight@0: tron@2010: if (cv->sprite == cursor) return; truelight@0: peter1138@5919: p = GetSprite(GB(cursor, 0, SPRITE_WIDTH)); truelight@0: cv->sprite = cursor; peter1138@5919: cv->pal = pal; tron@1348: cv->size.y = p->height; tron@1351: cv->size.x = p->width; tron@1351: cv->offs.x = p->x_offs; tron@1351: cv->offs.y = p->y_offs; truelight@0: truelight@0: cv->dirty = true; truelight@0: } truelight@0: rubidium@6573: static void SwitchAnimatedCursor() truelight@0: { rubidium@6464: const AnimCursor *cur = _cursor.animate_cur; truelight@0: rubidium@6464: if (cur == NULL || cur->sprite == AnimCursor::LAST) cur = _cursor.animate_list; Darkvater@1914: rubidium@6464: SetCursorSprite(cur->sprite, _cursor.pal); truelight@0: rubidium@6464: _cursor.animate_timeout = cur->display_time; rubidium@6464: _cursor.animate_cur = cur + 1; truelight@0: } truelight@0: rubidium@6573: void CursorTick() tron@1093: { tron@2010: if (_cursor.animate_timeout != 0 && --_cursor.animate_timeout == 0) truelight@0: SwitchAnimatedCursor(); truelight@0: } truelight@0: peter1138@5919: void SetMouseCursor(SpriteID sprite, SpriteID pal) truelight@0: { belugas@6505: /* Turn off animation */ truelight@0: _cursor.animate_timeout = 0; belugas@6505: /* Set cursor */ peter1138@5919: SetCursorSprite(sprite, pal); truelight@0: } truelight@0: rubidium@6464: void SetAnimatedMouseCursor(const AnimCursor *table) truelight@0: { truelight@0: _cursor.animate_list = table; truelight@0: _cursor.animate_cur = NULL; peter1138@5919: _cursor.pal = PAL_NONE; truelight@0: SwitchAnimatedCursor(); truelight@0: } truelight@0: rubidium@7856: bool ChangeResInGame(int width, int height) truelight@193: { rubidium@7858: return (_screen.width == width && _screen.height == height) || _video_driver->ChangeResolution(width, height); truelight@0: } darkvater@298: belugas@8667: bool ToggleFullScreen(bool fs) rubidium@5217: { belugas@8667: bool result = _video_driver->ToggleFullscreen(fs); rubidium@5217: if (_fullscreen != fs && _num_resolutions == 0) { Darkvater@5568: DEBUG(driver, 0, "Could not find a suitable fullscreen resolution"); rubidium@5217: } belugas@8667: return result; rubidium@5217: } Darkvater@1829: smatz@10983: static int CDECL compare_res(const Dimension *pa, const Dimension *pb) darkvater@298: { smatz@10983: int x = pa->width - pb->width; Darkvater@1806: if (x != 0) return x; smatz@10983: return pa->height - pb->height; Darkvater@1806: } Darkvater@1806: Darkvater@1806: void SortResolutions(int count) Darkvater@1806: { smatz@10983: QSortT(_resolutions, count, &compare_res); darkvater@298: }