tron@2186: /* $Id$ */ tron@2186: rubidium@9111: /** @file gfx.cpp Handling of drawing text and other gfx related stuff. */ belugas@6179: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" rubidium@8123: #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@8123: #include "zoom_func.h" truelight@6998: #include "texteff.hpp" truelight@6937: #include "blitter/factory.hpp" peter1138@7170: #include "video/video_driver.hpp" rubidium@8114: #include "strings_func.h" rubidium@8131: #include "core/math_func.hpp" rubidium@8270: #include "settings_type.h" rubidium@8985: #include "core/alloc_func.hpp" skidd13@9527: #include "core/sort_func.hpp" rubidium@9126: #include "landscape_type.h" rubidium@9898: #include "network/network_func.h" truelight@0: rubidium@8264: #include "table/palettes.h" rubidium@8264: #include "table/sprites.h" rubidium@8264: #include "table/control_codes.h" rubidium@8264: belugas@6179: byte _dirkeys; ///< 1 = left, 2 = up, 4 = right, 8 = down KUDr@5636: bool _fullscreen; KUDr@5636: CursorVars _cursor; belugas@6179: bool _ctrl_pressed; ///< Is Ctrl pressed? belugas@6179: bool _shift_pressed; ///< Is Shift pressed? KUDr@5636: byte _fast_forward; rubidium@8857: bool _left_button_down; ///< Is left mouse button pressed? rubidium@8857: bool _left_button_clicked; ///< Is left mouse button clicked? rubidium@8857: bool _right_button_down; ///< Is right mouse button pressed? rubidium@8857: bool _right_button_clicked; ///< Is right mouse button clicked? KUDr@5636: DrawPixelInfo _screen; frosch@8249: bool _screen_disable_anim = false; ///< Disable palette animation (important for 32bpp-anim blitter during giant screenshot) KUDr@5636: bool _exit_game; KUDr@5636: bool _networking; ///< are we in networking mode? KUDr@5636: byte _game_mode; peter1138@8670: int8 _pause_game; KUDr@5636: int _pal_first_dirty; truelight@6960: int _pal_count_dirty; KUDr@5636: tron@1991: Colour _cur_palette[256]; rubidium@9773: byte _stringwidth_table[FS_END][224]; ///< Cache containing width of often used characters. @see GetCharacterWidth() rubidium@8123: DrawPixelInfo *_cur_dpi; belugas@9850: byte _colour_gradient[COLOUR_END][8]; tron@1991: rubidium@7681: static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL); rubidium@10248: static int ReallyDoDrawString(const char *string, int x, int y, uint16 real_colour, bool parse_string_also_when_clipped = false); truelight@0: peter1138@3798: FontSize _cur_fontsize; peter1138@3798: static FontSize _last_fontsize; truelight@6878: static uint8 _cursor_backup[64 * 64 * 4]; rubidium@7545: rubidium@7545: /** rubidium@7545: * The rect for repaint. rubidium@7545: * rubidium@7545: * This rectangle defines the area which should be repaint by the video driver. rubidium@7545: * rubidium@7545: * @ingroup dirty rubidium@7545: */ truelight@0: static Rect _invalid_rect; tron@1357: static const byte *_color_remap_ptr; truelight@0: static byte _string_colorremap[3]; truelight@0: rubidium@8981: enum { rubidium@8981: DIRTY_BLOCK_HEIGHT = 8, rubidium@8981: DIRTY_BLOCK_WIDTH = 64, rubidium@8981: }; rubidium@8985: static uint _dirty_bytes_per_line = 0; rubidium@8985: 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@6937: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); truelight@0: tron@2010: if (xo == 0 && yo == 0) return; truelight@0: tron@2010: if (_cursor.visible) UndrawMouseCursor(); rubidium@9898: rubidium@9898: #ifdef ENABLE_NETWORK rubidium@9898: NetworkUndrawChatMessage(); rubidium@9898: #endif /* ENABLE_NETWORK */ truelight@0: truelight@6951: blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo); belugas@6179: /* This part of the screen is now dirty. */ peter1138@7170: _video_driver->MakeDirty(left, top, width, height); truelight@0: } truelight@0: truelight@0: frosch@9607: /** frosch@9607: * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen. frosch@9607: * frosch@9607: * @pre dpi->zoom == ZOOM_LVL_NORMAL, right >= left, bottom >= top frosch@9607: * @param left Minimum X (inclusive) frosch@9607: * @param top Minimum Y (inclusive) frosch@9607: * @param right Maximum X (inclusive) frosch@9607: * @param bottom Maximum Y (inclusive) frosch@9607: * @param color A 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolor spritenumber (FILLRECT_RECOLOR) frosch@9607: * @param mode frosch@9607: * FILLRECT_OPAQUE: Fill the rectangle with the specified color frosch@9607: * FILLRECT_CHECKER: Like FILLRECT_OPAQUE, but only draw every second pixel (used to grey out things) frosch@9607: * FILLRECT_RECOLOR: Apply a recolor sprite to every pixel in the rectangle currently on screen frosch@9607: */ frosch@9607: void GfxFillRect(int left, int top, int right, int bottom, int color, FillRectMode mode) tron@2010: { truelight@6937: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); truelight@6878: const DrawPixelInfo *dpi = _cur_dpi; truelight@6878: void *dst; tron@332: const int otop = top; tron@332: const int oleft = left; truelight@0: truelight@6624: 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@6937: dst = blitter->MoveTo(dpi->dst_ptr, left, top); truelight@0: frosch@9607: switch (mode) { frosch@9607: default: // FILLRECT_OPAQUE peter1138@6947: blitter->DrawRect(dst, right, bottom, (uint8)color); frosch@9607: break; frosch@9607: frosch@9607: case FILLRECT_RECOLOR: truelight@6937: blitter->DrawColorMappingRect(dst, right, bottom, GB(color, 0, PALETTE_WIDTH)); frosch@9607: break; frosch@9607: frosch@9607: case FILLRECT_CHECKER: { frosch@9607: byte bo = (oleft - left + dpi->left + otop - top + dpi->top) & 1; frosch@9607: do { frosch@9607: for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, (uint8)color); frosch@9607: dst = blitter->MoveTo(dst, 0, 1); frosch@9607: } while (--bottom > 0); frosch@9607: 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@6948: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); truelight@6948: DrawPixelInfo *dpi = _cur_dpi; truelight@0: truelight@6948: x -= dpi->left; truelight@6948: x2 -= dpi->left; truelight@6948: y -= dpi->top; truelight@6948: y2 -= dpi->top; truelight@0: truelight@6948: /* Check clipping */ truelight@6948: if (x < 0 && x2 < 0) return; truelight@6948: if (y < 0 && y2 < 0) return; truelight@6948: if (x > dpi->width && x2 > dpi->width) return; truelight@6948: if (y > dpi->height && y2 > dpi->height) return; truelight@6948: truelight@6948: blitter->DrawLine(dpi->dst_ptr, x, y, x2, y2, dpi->width, dpi->height, color); truelight@0: } truelight@0: rubidium@7651: void GfxDrawLineUnscaled(int x, int y, int x2, int y2, int color) rubidium@7651: { rubidium@7651: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); rubidium@7651: DrawPixelInfo *dpi = _cur_dpi; rubidium@7651: rubidium@7651: x -= dpi->left; rubidium@7651: x2 -= dpi->left; rubidium@7651: y -= dpi->top; rubidium@7651: y2 -= dpi->top; rubidium@7651: rubidium@7651: /* Check clipping */ rubidium@7651: if (x < 0 && x2 < 0) return; rubidium@7651: if (y < 0 && y2 < 0) return; rubidium@7651: if (x > dpi->width && x2 > dpi->width) return; rubidium@7651: if (y > dpi->height && y2 > dpi->height) return; rubidium@7651: rubidium@7651: blitter->DrawLine(dpi->dst_ptr, UnScaleByZoom(x, dpi->zoom), UnScaleByZoom(y, dpi->zoom), rubidium@7651: UnScaleByZoom(x2, dpi->zoom), UnScaleByZoom(y2, dpi->zoom), rubidium@7651: UnScaleByZoom(dpi->width, dpi->zoom), UnScaleByZoom(dpi->height, dpi->zoom), color); rubidium@7651: } rubidium@7651: rubidium@7643: /** rubidium@7643: * Draws the projection of a parallelepiped. rubidium@7643: * This can be used to draw boxes in world coordinates. rubidium@7643: * rubidium@7643: * @param x Screen X-coordinate of top front corner. rubidium@7643: * @param y Screen Y-coordinate of top front corner. rubidium@7643: * @param dx1 Screen X-length of first edge. rubidium@7643: * @param dy1 Screen Y-length of first edge. rubidium@7643: * @param dx2 Screen X-length of second edge. rubidium@7643: * @param dy2 Screen Y-length of second edge. rubidium@7643: * @param dx3 Screen X-length of third edge. rubidium@7643: * @param dy3 Screen Y-length of third edge. rubidium@7643: */ rubidium@7643: void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3) rubidium@7643: { rubidium@7643: /* .... rubidium@7643: * .. .... rubidium@7643: * .. .... rubidium@7643: * .. ^ rubidium@7643: * <--__(dx1,dy1) /(dx2,dy2) rubidium@7643: * : --__ / : rubidium@7643: * : --__ / : rubidium@7643: * : *(x,y) : rubidium@7643: * : | : rubidium@7643: * : | .. rubidium@7643: * .... |(dx3,dy3) rubidium@7643: * .... | .. rubidium@7643: * ....V. rubidium@7643: */ rubidium@7643: rubidium@7643: static const byte color = 255; rubidium@7643: rubidium@7651: GfxDrawLineUnscaled(x, y, x + dx1, y + dy1, color); rubidium@7651: GfxDrawLineUnscaled(x, y, x + dx2, y + dy2, color); rubidium@7651: GfxDrawLineUnscaled(x, y, x + dx3, y + dy3, color); rubidium@7643: rubidium@7651: GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx2, y + dy1 + dy2, color); rubidium@7651: GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx3, y + dy1 + dy3, color); rubidium@7651: GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx1, y + dy2 + dy1, color); rubidium@7651: GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx3, y + dy2 + dy3, color); rubidium@7651: GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx1, y + dy3 + dy1, color); rubidium@7651: GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx2, y + dy3 + dy2, color); rubidium@7643: } rubidium@7643: peter1138@3798: rubidium@10248: #if !defined(WITH_ICU) rubidium@10248: static void HandleBiDiAndArabicShapes(char *text, const char *lastof) {} rubidium@10248: #else rubidium@10415: #include rubidium@10415: #include rubidium@10248: rubidium@10248: /** rubidium@10248: * Function to be able to handle right-to-left text and Arabic chars properly. rubidium@10248: * rubidium@10248: * First: right-to-left (RTL) is stored 'logically' in almost all applications rubidium@10248: * and so do we. This means that their text is stored from right to the rubidium@10248: * left in memory and any non-RTL text (like numbers or English) are rubidium@10248: * then stored from left-to-right. When we want to actually draw the rubidium@10248: * text we need to reverse the RTL text in memory, which is what rubidium@10248: * happens in ubidi_writeReordered. rubidium@10248: * Second: Arabic characters "differ" based on their context. To draw the rubidium@10248: * correct variant we pass it through u_shapeArabic. This function can rubidium@10248: * add or remove some characters. This is the reason for the lastof rubidium@10248: * so we know till where we can fill the output. rubidium@10248: * rubidium@10248: * Sadly enough these functions work with a custom character format, UChar, rubidium@10248: * which isn't the same size as WChar. Because of that we need to transform rubidium@10248: * our text first to UChars and then back to something we can use. rubidium@10248: * rubidium@10248: * To be able to truncate strings properly you must truncate before passing to rubidium@10248: * this function. This way the logical begin of the string remains and the end rubidium@10248: * gets chopped of instead of the other way around. rubidium@10248: * rubidium@10248: * The reshaping of Arabic characters might increase or decrease the width of rubidium@10248: * the characters/string. So it might still overflow after truncation, though rubidium@10248: * the chance is fairly slim as most characters get shorter instead of longer. rubidium@10248: * @param buffer the buffer to read from/to rubidium@10248: * @param lastof the end of the buffer rubidium@10248: */ rubidium@10248: static void HandleBiDiAndArabicShapes(char *buffer, const char *lastof) rubidium@10248: { rubidium@10248: UChar input_output[DRAW_STRING_BUFFER]; rubidium@10248: UChar intermediate[DRAW_STRING_BUFFER]; rubidium@10248: rubidium@10248: char *t = buffer; rubidium@10248: size_t length = 0; rubidium@10248: while (*t != '\0' && length < lengthof(input_output)) { rubidium@10248: WChar tmp; rubidium@10248: t += Utf8Decode(&tmp, t); rubidium@10248: input_output[length++] = tmp; rubidium@10248: } rubidium@10248: input_output[length] = 0; rubidium@10248: rubidium@10248: UErrorCode err = U_ZERO_ERROR; rubidium@10263: UBiDi *para = ubidi_openSized((int32_t)length, 0, &err); rubidium@10248: if (para == NULL) return; rubidium@10248: rubidium@10278: ubidi_setPara(para, input_output, (int32_t)length, _dynlang.text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, NULL, &err); rubidium@10263: ubidi_writeReordered(para, intermediate, (int32_t)length, 0, &err); rubidium@10263: length = u_shapeArabic(intermediate, (int32_t)length, input_output, lengthof(input_output), U_SHAPE_TEXT_DIRECTION_VISUAL_LTR | U_SHAPE_LETTERS_SHAPE, &err); rubidium@10248: ubidi_close(para); rubidium@10248: rubidium@10248: if (U_FAILURE(err)) return; rubidium@10248: rubidium@10248: t = buffer; rubidium@10248: for (size_t i = 0; i < length && t < (lastof - 4); i++) { rubidium@10248: t += Utf8Encode(t, input_output[i]); rubidium@10248: } rubidium@10248: *t = '\0'; rubidium@10248: } rubidium@10248: #endif /* WITH_ICU */ rubidium@10248: rubidium@10248: 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@6481: * @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@9637: /* string got too big... insert dotdotdot, but make sure we do not rubidium@9637: * print anything beyond the string termination character. */ rubidium@9637: for (int i = 0; *ddd_pos != '\0' && i < 3; i++, ddd_pos++) *ddd_pos = '.'; rubidium@9637: *ddd_pos = '\0'; Darkvater@2097: return ddd_w; Darkvater@2097: } Darkvater@2097: } else { rubidium@9636: if (c == SCC_SETX) { rubidium@9636: w = *str; rubidium@9636: str++; rubidium@9636: } else if (c == SCC_SETXY) { rubidium@9636: w = *str; rubidium@9636: str += 2; rubidium@9636: } 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@6179: /* 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: rubidium@9773: /** rubidium@9773: * Write string to output buffer, truncating it to specified maximal width in pixels if it is too long. rubidium@9773: * rubidium@9773: * @param src String to truncate rubidium@9773: * @param dest Start of character output buffer where truncated string is stored rubidium@9773: * @param maxw Maximal allowed length of the string in pixels rubidium@9773: * @param last Address of last character in output buffer rubidium@9773: * rubidium@9773: * @return Actual width of the (possibly) truncated string in pixels rubidium@9773: */ 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: rubidium@9773: /** rubidium@9773: * Draw string starting at position (x,y). rubidium@9773: * rubidium@9773: * @param x X position to start drawing rubidium@9773: * @param y Y position to start drawing rubidium@9773: * @param str String to draw rubidium@9773: * @param color Color used for drawing the string, see DoDrawString() for details rubidium@9773: * rubidium@9773: * @return Horizontal coordinate after drawing the string rubidium@9773: */ Darkvater@2097: int DrawString(int x, int y, StringID str, uint16 color) truelight@0: { rubidium@9901: char buffer[DRAW_STRING_BUFFER]; tron@1336: Darkvater@4912: GetString(buffer, str, lastof(buffer)); rubidium@10248: HandleBiDiAndArabicShapes(buffer, lastof(buffer)); rubidium@10248: return ReallyDoDrawString(buffer, x, y, color); truelight@0: } truelight@0: rubidium@9773: /** rubidium@9773: * Draw string, possibly truncated to make it fit in its allocated space rubidium@9773: * rubidium@9773: * @param x X position to start drawing rubidium@9773: * @param y Y position to start drawing rubidium@9773: * @param str String to draw rubidium@9773: * @param color Color used for drawing the string, see DoDrawString() for details rubidium@9773: * @param maxw Maximal width of the string rubidium@9773: * rubidium@9773: * @return Horizontal coordinate after drawing the (possibly truncated) string rubidium@9773: */ Darkvater@2097: int DrawStringTruncated(int x, int y, StringID str, uint16 color, uint maxw) Darkvater@2097: { rubidium@9901: char buffer[DRAW_STRING_BUFFER]; Darkvater@4912: TruncateStringID(str, buffer, maxw, lastof(buffer)); rubidium@10248: HandleBiDiAndArabicShapes(buffer, lastof(buffer)); rubidium@10248: return ReallyDoDrawString(buffer, x, y, color); Darkvater@2097: } truelight@0: rubidium@9773: /** rubidium@9773: * Draw string right-aligned. rubidium@9773: * rubidium@9773: * @param x Right-most x position of the string rubidium@9773: * @param y Y position of the string rubidium@9773: * @param str String to draw rubidium@9773: * @param color Color used for drawing the string, see DoDrawString() for details rubidium@9773: * rubidium@9773: * @return Width of drawn string in pixels rubidium@9773: */ rubidium@4314: int DrawStringRightAligned(int x, int y, StringID str, uint16 color) truelight@0: { rubidium@9901: char buffer[DRAW_STRING_BUFFER]; rubidium@4314: int w; tron@1336: Darkvater@4912: GetString(buffer, str, lastof(buffer)); rubidium@10248: HandleBiDiAndArabicShapes(buffer, lastof(buffer)); rubidium@10248: Darkvater@4609: w = GetStringBoundingBox(buffer).width; rubidium@10248: ReallyDoDrawString(buffer, x - w, y, color); rubidium@4314: rubidium@4314: return w; truelight@0: } truelight@0: rubidium@9773: /** rubidium@9773: * Draw string right-aligned, possibly truncated to make it fit in its allocated space rubidium@9773: * rubidium@9773: * @param x Right-most x position to start drawing rubidium@9773: * @param y Y position to start drawing rubidium@9773: * @param str String to draw rubidium@9773: * @param color Color used for drawing the string, see DoDrawString() for details rubidium@9773: * @param maxw Maximal width of the string rubidium@9773: */ Darkvater@2097: void DrawStringRightAlignedTruncated(int x, int y, StringID str, uint16 color, uint maxw) Darkvater@2097: { rubidium@9901: char buffer[DRAW_STRING_BUFFER]; truelight@0: Darkvater@4912: TruncateStringID(str, buffer, maxw, lastof(buffer)); rubidium@10248: HandleBiDiAndArabicShapes(buffer, lastof(buffer)); rubidium@10248: ReallyDoDrawString(buffer, x - GetStringBoundingBox(buffer).width, y, color); Darkvater@2097: } Darkvater@2097: rubidium@9773: /** rubidium@9773: * Draw string right-aligned with a line underneath it. rubidium@9773: * rubidium@9773: * @param x Right-most x position of the string rubidium@9773: * @param y Y position of the string rubidium@9773: * @param str String to draw rubidium@9773: * @param color Color used for drawing the string, see DoDrawString() for details rubidium@9773: */ 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: rubidium@9773: /** rubidium@9773: * Draw string centered. rubidium@9773: * rubidium@9773: * @param x X position of center of the string rubidium@9773: * @param y Y position of center of the string rubidium@9773: * @param str String to draw rubidium@9773: * @param color Color used for drawing the string, see DoDrawString() for details rubidium@9773: * rubidium@9773: * @return Width of the drawn string in pixels rubidium@9773: */ Darkvater@2097: int DrawStringCentered(int x, int y, StringID str, uint16 color) truelight@0: { rubidium@9901: char buffer[DRAW_STRING_BUFFER]; truelight@0: int w; truelight@0: Darkvater@4912: GetString(buffer, str, lastof(buffer)); rubidium@10248: HandleBiDiAndArabicShapes(buffer, lastof(buffer)); truelight@0: Darkvater@4609: w = GetStringBoundingBox(buffer).width; rubidium@10248: ReallyDoDrawString(buffer, x - w / 2, y, color); truelight@0: truelight@0: return w; truelight@0: } truelight@0: rubidium@9773: /** rubidium@9773: * Draw string centered, possibly truncated to fit in the assigned space. rubidium@9773: * rubidium@9773: * @param xl Left-most x position rubidium@9773: * @param xr Right-most x position rubidium@9773: * @param y Y position of the string rubidium@9773: * @param str String to draw rubidium@9773: * @param color Color used for drawing the string, see DoDrawString() for details rubidium@9773: * rubidium@9773: * @return Right-most coordinate of the (possibly truncated) drawn string rubidium@9773: */ Darkvater@2113: int DrawStringCenteredTruncated(int xl, int xr, int y, StringID str, uint16 color) Darkvater@2097: { rubidium@9901: char buffer[DRAW_STRING_BUFFER]; rubidium@10248: TruncateStringID(str, buffer, xr - xl, lastof(buffer)); rubidium@10248: HandleBiDiAndArabicShapes(buffer, lastof(buffer)); rubidium@10248: rubidium@10248: int w = GetStringBoundingBox(buffer).width; rubidium@10248: return ReallyDoDrawString(buffer, (xl + xr - w) / 2, y, color); Darkvater@2097: } Darkvater@2097: rubidium@9773: /** rubidium@9773: * Draw string centered. rubidium@9773: * rubidium@9773: * @param x X position of center of the string rubidium@9773: * @param y Y position of center of the string rubidium@9773: * @param str String to draw rubidium@9773: * @param color Color used for drawing the string, see DoDrawString() for details rubidium@9773: * rubidium@9773: * @return Width of the drawn string in pixels rubidium@9773: */ Darkvater@2134: int DoDrawStringCentered(int x, int y, const char *str, uint16 color) Darkvater@2134: { rubidium@10248: char buffer[DRAW_STRING_BUFFER]; rubidium@10248: strecpy(buffer, str, lastof(buffer)); rubidium@10248: HandleBiDiAndArabicShapes(buffer, lastof(buffer)); rubidium@10248: rubidium@10248: int w = GetStringBoundingBox(buffer).width; rubidium@10248: ReallyDoDrawString(buffer, x - w / 2, y, color); Darkvater@2134: return w; Darkvater@2134: } Darkvater@2134: rubidium@9773: /** rubidium@9773: * Draw string centered, with additional line underneath it rubidium@9773: * rubidium@9773: * @param x X position of center of the string rubidium@9773: * @param y Y position of center of the string rubidium@9773: * @param str String to draw rubidium@9773: * @param color Color used for drawing the string, see DoDrawString() for details rubidium@9773: */ 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: rubidium@9773: /** rubidium@9773: * Draw string centered possibly truncated, with additional line underneath it rubidium@9773: * rubidium@9773: * @param xl Left x position of the string rubidium@9773: * @param xr Right x position of the string rubidium@9773: * @param y Y position of center of the string rubidium@9773: * @param str String to draw rubidium@9773: * @param color Color used for drawing the string, see DoDrawString() for details rubidium@9773: */ 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@7545: /** rubidium@7545: * '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@6481: * 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@6481: * @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@7545: * 16 - 31 the fontsize in which the length calculation was done at rubidium@7545: */ 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@6215: 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@6215: *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@6491: 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@6215: /* String didn't fit on line (or a '\n' was encountered), so 'dummy' terminate Darkvater@6215: * and increase linecount. We use Utf8PrevChar() as also non 1 char long Darkvater@6215: * whitespace seperators are supported */ truelight@0: num++; Darkvater@6215: char *s = Utf8PrevChar(str); Darkvater@6215: *s++ = '\0'; Darkvater@6215: Darkvater@6215: /* In which case (see above) we will shift remainder to left and close the gap */ Darkvater@6215: if (str - s >= 1) { Darkvater@6215: for (; str[-1] != '\0';) *s++ = *str++; Darkvater@6215: } truelight@0: } truelight@0: } truelight@0: smatz@9682: smatz@9682: /** Calculates height of string (in pixels). Accepts multiline string with '\0' as separators. smatz@9682: * @param src string to check smatz@9682: * @param num number of extra lines (output of FormatStringLinebreaks()) smatz@9682: * @note assumes text won't be truncated. FormatStringLinebreaks() is a good way to ensure that. smatz@9682: * @return height of pixels of string when it is drawn smatz@9682: */ smatz@9682: static int GetMultilineStringHeight(const char *src, int num) smatz@9682: { smatz@9682: int maxy = 0; smatz@9682: int y = 0; smatz@9682: int fh = GetCharacterHeight(_cur_fontsize); smatz@9682: smatz@9682: for (;;) { smatz@9682: WChar c = Utf8Consume(&src); smatz@9682: smatz@9682: switch (c) { smatz@9682: case 0: y += fh; if (--num < 0) return maxy; break; smatz@9682: case '\n': y += fh; break; smatz@9682: case SCC_SETX: src++; break; smatz@9682: case SCC_SETXY: src++; y = (int)*src++; break; smatz@9682: case SCC_TINYFONT: fh = GetCharacterHeight(FS_SMALL); break; smatz@9682: case SCC_BIGFONT: fh = GetCharacterHeight(FS_LARGE); break; smatz@9682: default: maxy = max(maxy, y + fh); break; smatz@9682: } smatz@9682: } smatz@9682: } smatz@9682: smatz@9682: smatz@9682: /** Calculates height of string (in pixels). The string is changed to a multiline string if needed. smatz@9682: * @param str string to check smatz@9682: * @param maxw maximum string width smatz@9682: * @return height of pixels of string when it is drawn smatz@9682: */ smatz@9682: int GetStringHeight(StringID str, int maxw) smatz@9682: { rubidium@9901: char buffer[DRAW_STRING_BUFFER]; smatz@9682: smatz@9682: GetString(buffer, str, lastof(buffer)); smatz@9682: smatz@9682: uint32 tmp = FormatStringLinebreaks(buffer, maxw); smatz@9682: smatz@9682: return GetMultilineStringHeight(buffer, GB(tmp, 0, 16)); smatz@9682: } smatz@9682: smatz@9682: frosch@9322: /** Draw a given string with the centre around the given (x,y) coordinates Darkvater@5391: * @param x Centre the string around this pixel width frosch@9322: * @param y Centre the string around this pixel height Darkvater@5391: * @param str String to draw belugas@6481: * @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: { rubidium@9901: char buffer[DRAW_STRING_BUFFER]; truelight@0: uint32 tmp; rubidium@10248: int num, 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@5587: 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 (;;) { rubidium@10248: char buf2[DRAW_STRING_BUFFER]; rubidium@10248: strecpy(buf2, src, lastof(buf2)); rubidium@10248: HandleBiDiAndArabicShapes(buf2, lastof(buf2)); rubidium@10248: int w = GetStringBoundingBox(buf2).width; rubidium@10248: ReallyDoDrawString(buf2, x - (w >> 1), y, 0xFE, true); 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@6491: src += 2; truelight@0: } truelight@0: } truelight@0: } truelight@0: } truelight@0: peter1138@4928: maedhros@6377: uint DrawStringMultiLine(int x, int y, StringID str, int maxw, int maxh) tron@2010: { rubidium@9901: char buffer[DRAW_STRING_BUFFER]; 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@5587: mt = GetCharacterHeight((FontSize)GB(tmp, 16, 16)); peter1138@4928: total_height = (num + 1) * mt; truelight@0: maedhros@6382: if (maxh != -1 && (int)total_height > maxh) { maedhros@6382: /* Check there's room enough for at least one line. */ maedhros@6382: if (maxh < mt) return 0; maedhros@6382: maedhros@6377: num = maxh / mt - 1; maedhros@6377: total_height = (num + 1) * mt; maedhros@6377: } maedhros@6377: tron@1336: src = buffer; truelight@0: tron@2952: for (;;) { rubidium@10248: char buf2[DRAW_STRING_BUFFER]; rubidium@10248: strecpy(buf2, src, lastof(buf2)); rubidium@10248: HandleBiDiAndArabicShapes(buf2, lastof(buf2)); rubidium@10248: ReallyDoDrawString(buf2, x, y, 0xFE, true); 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@6491: 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@8121: * 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@8121: Dimension GetStringBoundingBox(const char *str) tron@1323: { peter1138@3798: FontSize size = _cur_fontsize; rubidium@8121: 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@5391: case SCC_SETX: br.width += (byte)*str++; break; peter1138@5108: case SCC_SETXY: Darkvater@5391: br.width += (byte)*str++; Darkvater@5391: 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@9773: /** rubidium@9773: * Draw single character horizontally centered around (x,y) rubidium@9773: * @param c Character (glyph) to draw rubidium@9773: * @param x X position to draw character rubidium@9773: * @param y Y position to draw character rubidium@9773: * @param real_color Colour to use, see DoDrawString() for details rubidium@9773: */ rubidium@8737: void DrawCharCentered(WChar c, int x, int y, uint16 real_color) rubidium@8737: { rubidium@8737: FontSize size = FS_NORMAL; rubidium@8737: byte color = real_color & 0xFF; rubidium@8737: int w = GetCharacterWidth(size, c); rubidium@8737: rubidium@9989: _string_colorremap[1] = _string_colormap[_use_palette][color].text; rubidium@9989: _string_colorremap[2] = _string_colormap[_use_palette][color].shadow; rubidium@8737: _color_remap_ptr = _string_colorremap; rubidium@8737: rubidium@8737: GfxMainBlitter(GetGlyph(size, c), x - w / 2, y, BM_COLOUR_REMAP); rubidium@8737: } rubidium@8737: belugas@9879: /** Draw a string at the given coordinates with the given colour. belugas@9879: * While drawing the string, parse it in case some formatting is specified, belugas@9879: * like new colour, new size or even positionning. rubidium@10248: * @param string The string to draw. This is not yet bidi reordered. rubidium@9904: * @param x Offset from left side of the screen rubidium@9904: * @param y Offset from top side of the screen belugas@9879: * @param real_colour Colour of the string, see _string_colormap in belugas@9879: * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h frosch@9882: * @param parse_string_also_when_clipped frosch@9882: * By default, always test the available space where to draw the string. frosch@9882: * When in multipline drawing, it would already be done, frosch@9882: * so no need to re-perform the same kind (more or less) of verifications. frosch@9882: * It's not only an optimisation, it's also a way to ensures the string will be parsed frosch@9882: * (as there are certain side effects on global variables, which are important for the next line) belugas@9879: * @return the x-coordinates where the drawing has finished. belugas@9879: * If nothing is drawn, the originally passed x-coordinate is returned belugas@9879: */ frosch@9882: int DoDrawString(const char *string, int x, int y, uint16 real_colour, bool parse_string_also_when_clipped) tron@1323: { rubidium@10248: char buffer[DRAW_STRING_BUFFER]; rubidium@10248: strecpy(buffer, string, lastof(buffer)); rubidium@10248: HandleBiDiAndArabicShapes(buffer, lastof(buffer)); rubidium@10248: rubidium@10248: return ReallyDoDrawString(buffer, x, y, real_colour, parse_string_also_when_clipped); rubidium@10248: } rubidium@10248: rubidium@10248: /** Draw a string at the given coordinates with the given colour. rubidium@10248: * While drawing the string, parse it in case some formatting is specified, rubidium@10248: * like new colour, new size or even positionning. rubidium@10248: * @param string The string to draw. This is already bidi reordered. rubidium@10248: * @param x Offset from left side of the screen rubidium@10248: * @param y Offset from top side of the screen rubidium@10248: * @param real_colour Colour of the string, see _string_colormap in rubidium@10248: * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h rubidium@10248: * @param parse_string_also_when_clipped rubidium@10248: * By default, always test the available space where to draw the string. rubidium@10248: * When in multipline drawing, it would already be done, rubidium@10248: * so no need to re-perform the same kind (more or less) of verifications. rubidium@10248: * It's not only an optimisation, it's also a way to ensures the string will be parsed rubidium@10248: * (as there are certain side effects on global variables, which are important for the next line) rubidium@10248: * @return the x-coordinates where the drawing has finished. rubidium@10248: * If nothing is drawn, the originally passed x-coordinate is returned rubidium@10248: */ rubidium@10248: static int ReallyDoDrawString(const char *string, int x, int y, uint16 real_colour, bool parse_string_also_when_clipped) rubidium@10248: { 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: belugas@9879: byte colour = real_colour & 0xFF; // extract the 8 bits colour index that is required for the mapping belugas@9879: byte previous_colour = colour; truelight@543: frosch@9882: if (!parse_string_also_when_clipped) { belugas@9879: /* in "mode multiline", the available space have been verified. Not in regular one. belugas@9879: * So if the string cannot be drawn, return the original start to say so.*/ rubidium@9904: if (x >= dpi->left + dpi->width || y >= dpi->top + dpi->height) return x; truelight@0: belugas@9879: if (colour != TC_INVALID) { // the invalid colour flag test should not really occur. But better be safe belugas@9879: switch_colour:; belugas@9879: if (real_colour & IS_PALETTE_COLOR) { belugas@9879: _string_colorremap[1] = colour; rubidium@9989: _string_colorremap[2] = (_use_palette == PAL_DOS) ? 1 : 215; truelight@543: } else { rubidium@9989: _string_colorremap[1] = _string_colormap[_use_palette][colour].text; rubidium@9989: _string_colorremap[2] = _string_colormap[_use_palette][colour].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; belugas@9879: return x; // Nothing more to draw, get out. And here is the new x position 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@6852: GfxMainBlitter(GetGlyph(size, c), x, y, BM_COLOUR_REMAP); truelight@0: } peter1138@3798: x += GetCharacterWidth(size, c); peter1138@5108: } else if (c == '\n') { // newline = {} belugas@9879: x = xo; // We require a new line, so the x coordinate is reset peter1138@3798: y += GetCharacterHeight(size); truelight@0: goto check_bounds; belugas@9879: } else if (c >= SCC_BLUE && c <= SCC_BLACK) { // change colour? belugas@9879: previous_colour = colour; belugas@9879: colour = (byte)(c - SCC_BLUE); belugas@9879: goto switch_colour; belugas@9879: } else if (c == SCC_PREVIOUS_COLOUR) { // revert to the previous colour belugas@9879: Swap(colour, previous_colour); belugas@9879: goto switch_colour; 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@5391: DEBUG(misc, 0, "[utf8] unknown string command character %d", c); truelight@0: } truelight@0: } truelight@0: } truelight@0: rubidium@9773: /** rubidium@9773: * Draw the string of the character buffer, starting at position (x,y) with a given maximal width. rubidium@9773: * String is truncated if it is too long. rubidium@9773: * rubidium@9773: * @param str Character buffer containing the string rubidium@9773: * @param x Left-most x coordinate to start drawing rubidium@9773: * @param y Y coordinate to draw the string rubidium@9773: * @param color Colour to use, see DoDrawString() for details. rubidium@9773: * @param maxw Maximal width in pixels that may be used for drawing rubidium@9773: * rubidium@9773: * @return Right-most x position after drawing the (possibly truncated) string rubidium@9773: */ Darkvater@2097: int DoDrawStringTruncated(const char *str, int x, int y, uint16 color, uint maxw) Darkvater@2097: { rubidium@9901: char buffer[DRAW_STRING_BUFFER]; skidd13@10310: strecpy(buffer, str, lastof(buffer)); Darkvater@2097: TruncateString(buffer, maxw); Darkvater@2097: return DoDrawString(buffer, x, y, color); Darkvater@2097: } Darkvater@2097: rubidium@9773: /** rubidium@9773: * Draw a sprite. rubidium@9773: * @param img Image number to draw rubidium@9773: * @param pal Palette to use. rubidium@9773: * @param x Left coordinate of image rubidium@9773: * @param y Top coordinate of image rubidium@9773: * @param sub If available, draw only specified part of the sprite rubidium@9773: */ rubidium@7681: void DrawSprite(SpriteID img, SpriteID pal, int x, int y, const SubSprite *sub) tron@2010: { skidd13@7928: if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) { rubidium@10056: _color_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1; rubidium@10056: GfxMainBlitter(GetSprite(GB(img, 0, SPRITE_WIDTH), ST_NORMAL), x, y, BM_TRANSPARENT, sub); peter1138@5668: } else if (pal != PAL_NONE) { rubidium@10056: _color_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1; rubidium@10056: GfxMainBlitter(GetSprite(GB(img, 0, SPRITE_WIDTH), ST_NORMAL), x, y, BM_COLOUR_REMAP, sub); truelight@0: } else { rubidium@10056: GfxMainBlitter(GetSprite(GB(img, 0, SPRITE_WIDTH), ST_NORMAL), x, y, BM_NORMAL, sub); truelight@0: } truelight@0: } truelight@0: smatz@9511: 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@6852: Blitter::BlitterParams bp; truelight@0: rubidium@7681: /* Amount of pixels to clip from the source sprite */ rubidium@7681: int clip_left = (sub != NULL ? max(0, -sprite->x_offs + sub->left ) : 0); rubidium@7681: int clip_top = (sub != NULL ? max(0, -sprite->y_offs + sub->top ) : 0); rubidium@7681: int clip_right = (sub != NULL ? max(0, sprite->width - (-sprite->x_offs + sub->right + 1)) : 0); rubidium@7681: int clip_bottom = (sub != NULL ? max(0, sprite->height - (-sprite->y_offs + sub->bottom + 1)) : 0); rubidium@7681: rubidium@7681: if (clip_left + clip_right >= sprite->width) return; rubidium@7681: if (clip_top + clip_bottom >= sprite->height) return; rubidium@7681: truelight@6852: /* Move to the correct offset */ tron@1351: x += sprite->x_offs; tron@1351: y += sprite->y_offs; truelight@6852: truelight@6852: /* Copy the main data directly from the sprite */ tron@4518: bp.sprite = sprite->data; truelight@6852: bp.sprite_width = sprite->width; truelight@6852: bp.sprite_height = sprite->height; rubidium@7681: bp.width = UnScaleByZoom(sprite->width - clip_left - clip_right, dpi->zoom); rubidium@7681: bp.height = UnScaleByZoom(sprite->height - clip_top - clip_bottom, dpi->zoom); truelight@6852: bp.top = 0; truelight@6852: bp.left = 0; smatz@9511: bp.skip_left = UnScaleByZoomLower(clip_left, dpi->zoom); smatz@9511: bp.skip_top = UnScaleByZoomLower(clip_top, dpi->zoom); rubidium@7681: rubidium@7681: x += ScaleByZoom(bp.skip_left, dpi->zoom); rubidium@7681: y += ScaleByZoom(bp.skip_top, dpi->zoom); rubidium@7681: truelight@0: bp.dst = dpi->dst_ptr; truelight@0: bp.pitch = dpi->pitch; truelight@6852: bp.remap = _color_remap_ptr; truelight@0: truelight@6852: assert(sprite->width > 0); truelight@6852: assert(sprite->height > 0); truelight@6852: truelight@6852: if (bp.width <= 0) return; truelight@6852: if (bp.height <= 0) return; truelight@6852: truelight@6852: y -= dpi->top; truelight@6852: /* Check for top overflow */ truelight@6852: if (y < 0) { truelight@6852: bp.height -= -UnScaleByZoom(y, dpi->zoom); truelight@6852: if (bp.height <= 0) return; truelight@6852: bp.skip_top += -UnScaleByZoom(y, dpi->zoom); truelight@6852: y = 0; truelight@6852: } else { truelight@6852: bp.top = UnScaleByZoom(y, dpi->zoom); truelight@6804: } truelight@0: truelight@6852: /* Check for bottom overflow */ truelight@6852: y += ScaleByZoom(bp.height, dpi->zoom) - dpi->height; truelight@6852: if (y > 0) { truelight@6852: bp.height -= UnScaleByZoom(y, dpi->zoom); truelight@6804: if (bp.height <= 0) return; truelight@6804: } truelight@6804: truelight@6852: x -= dpi->left; truelight@6852: /* Check for left overflow */ truelight@6852: if (x < 0) { truelight@6852: bp.width -= -UnScaleByZoom(x, dpi->zoom); truelight@6804: if (bp.width <= 0) return; truelight@6852: bp.skip_left += -UnScaleByZoom(x, dpi->zoom); truelight@6804: x = 0; truelight@6852: } else { truelight@6852: bp.left = UnScaleByZoom(x, dpi->zoom); truelight@6804: } truelight@6804: truelight@6852: /* Check for right overflow */ truelight@6852: x += ScaleByZoom(bp.width, dpi->zoom) - dpi->width; truelight@6852: if (x > 0) { truelight@6852: bp.width -= UnScaleByZoom(x, dpi->zoom); truelight@6804: if (bp.width <= 0) return; truelight@6804: } truelight@6804: smatz@9511: assert(bp.skip_left + bp.width <= UnScaleByZoom(sprite->width, dpi->zoom)); smatz@9511: assert(bp.skip_top + bp.height <= UnScaleByZoom(sprite->height, dpi->zoom)); smatz@9511: truelight@6852: BlitterFactoryBase::GetCurrentBlitter()->Draw(&bp, mode, dpi->zoom); truelight@0: } truelight@0: rubidium@6247: void DoPaletteAnimations(); dominik@614: rubidium@6247: void GfxInitPalettes() truelight@0: { rubidium@9989: memcpy(_cur_palette, _palettes[_use_palette], sizeof(_cur_palette)); dominik@614: truelight@6964: DoPaletteAnimations(); truelight@0: _pal_first_dirty = 0; glx@7392: _pal_count_dirty = 256; truelight@0: } truelight@0: truelight@7018: #define EXTR(p, q) (((uint16)(_palette_animation_counter * (p)) * (q)) >> 16) truelight@7018: #define EXTR2(p, q) (((uint16)(~_palette_animation_counter * (p)) * (q)) >> 16) truelight@0: rubidium@6247: void DoPaletteAnimations() truelight@0: { truelight@6960: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); belugas@4171: const Colour *s; belugas@9911: const ExtraPaletteValues *ev = &_extra_palette_values; 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. */ rubidium@9989: const int colour_rotation_amount = (_use_palette == PAL_DOS) ? PALETTE_ANIM_SIZE_DOS : PALETTE_ANIM_SIZE_WIN; belugas@9870: Colour old_val[PALETTE_ANIM_SIZE_DOS]; belugas@9900: const int oldval_size = colour_rotation_amount * sizeof(*old_val); belugas@9911: const uint old_tc = _palette_animation_counter; tron@2005: uint i; tron@2005: uint j; truelight@6878: truelight@6960: if (blitter != NULL && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) { truelight@7018: _palette_animation_counter = 0; truelight@6878: } truelight@0: belugas@9895: Colour *palette_pos = &_cur_palette[PALETTE_ANIM_SIZE_START]; // Points to where animations are taking place on the palette belugas@9895: /* Makes a copy of the current anmation palette in old_val, belugas@9895: * so the work on the current palette could be compared, see if there has been any changes */ belugas@9900: memcpy(old_val, palette_pos, oldval_size); truelight@0: belugas@6179: /* Dark blue water */ belugas@9894: s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->dark_water_TOY : ev->dark_water; tron@2005: j = EXTR(320, 5); tron@2005: for (i = 0; i != 5; i++) { belugas@9895: *palette_pos++ = s[j]; tron@1991: j++; tron@1991: if (j == 5) j = 0; truelight@0: } truelight@0: belugas@6179: /* Glittery water */ belugas@9894: s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->glitter_water_TOY : ev->glitter_water; tron@1991: j = EXTR(128, 15); tron@2005: for (i = 0; i != 5; i++) { belugas@9895: *palette_pos++ = s[j]; tron@1991: j += 3; tron@1991: if (j >= 15) j -= 15; truelight@0: } truelight@0: belugas@9894: /* Fizzy Drink bubbles animation */ belugas@9894: s = ev->fizzy_drink; tron@1991: j = EXTR2(512, 5); tron@2005: for (i = 0; i != 5; i++) { belugas@9895: *palette_pos++ = s[j]; tron@1991: j++; tron@1991: if (j == 5) j = 0; truelight@0: } truelight@0: belugas@6179: /* Oil refinery fire animation */ truelight@0: s = ev->oil_ref; tron@1991: j = EXTR2(512, 7); tron@2005: for (i = 0; i != 7; i++) { belugas@9895: *palette_pos++ = s[j]; tron@1991: j++; tron@1991: if (j == 7) j = 0; truelight@0: } truelight@0: belugas@6179: /* Radio tower blinking */ truelight@0: { truelight@7018: 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); belugas@9895: palette_pos->r = v; belugas@9895: palette_pos->g = 0; belugas@9895: palette_pos->b = 0; belugas@9895: palette_pos++; truelight@0: truelight@0: i ^= 0x40; truelight@0: (v = 255, i < 0x3f) || truelight@0: (v = 128, i < 0x4A || i >= 0x75) || truelight@0: (v = 20); belugas@9895: palette_pos->r = v; belugas@9895: palette_pos->g = 0; belugas@9895: palette_pos->b = 0; belugas@9895: palette_pos++; truelight@0: } truelight@0: belugas@6179: /* Handle lighthouse and stadium animation */ truelight@0: s = ev->lighthouse; tron@1991: j = EXTR(256, 4); tron@2005: for (i = 0; i != 4; i++) { belugas@9895: *palette_pos++ = s[j]; tron@1991: j++; tron@1991: if (j == 4) j = 0; truelight@0: } truelight@0: belugas@6179: /* Animate water for old DOS graphics */ rubidium@9989: if (_use_palette == PAL_DOS) { belugas@6179: /* Dark blue water DOS */ belugas@9894: s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->dark_water_TOY : ev->dark_water; tron@2005: j = EXTR(320, 5); tron@2005: for (i = 0; i != 5; i++) { belugas@9895: *palette_pos++ = s[j]; tron@1991: j++; tron@1991: if (j == 5) j = 0; dominik@614: } tron@915: belugas@6179: /* Glittery water DOS */ belugas@9894: s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->glitter_water_TOY : ev->glitter_water; tron@1991: j = EXTR(128, 15); tron@2005: for (i = 0; i != 5; i++) { belugas@9895: *palette_pos++ = s[j]; tron@1991: j += 3; tron@1991: if (j >= 15) j -= 15; dominik@614: } dominik@614: } dominik@614: truelight@6960: if (blitter != NULL && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) { truelight@7018: _palette_animation_counter = old_tc; truelight@6960: } else { belugas@9900: if (memcmp(old_val, &_cur_palette[PALETTE_ANIM_SIZE_START], oldval_size) != 0) { belugas@9895: /* Did we changed anything on the palette? Seems so. Mark it as dirty */ belugas@9870: _pal_first_dirty = PALETTE_ANIM_SIZE_START; belugas@9900: _pal_count_dirty = colour_rotation_amount; truelight@6960: } truelight@0: } truelight@0: } truelight@0: truelight@0: rubidium@9773: /** Initialize _stringwidth_table cache */ rubidium@6247: 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: rubidium@9773: /** rubidium@9773: * Return width of character glyph. rubidium@9773: * @param size Font of the character rubidium@9773: * @param key Character code glyph rubidium@9773: * @return Width of the character glyph rubidium@9773: */ peter1138@5108: byte GetCharacterWidth(FontSize size, WChar key) peter1138@5108: { rubidium@9773: /* Use _stringwidth_table cache if possible */ 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@6247: void ScreenSizeChanged() truelight@0: { rubidium@8985: _dirty_bytes_per_line = (_screen.width + DIRTY_BLOCK_WIDTH - 1) / DIRTY_BLOCK_WIDTH; rubidium@8985: _dirty_blocks = ReallocT(_dirty_blocks, _dirty_bytes_per_line * ((_screen.height + DIRTY_BLOCK_HEIGHT - 1) / DIRTY_BLOCK_HEIGHT)); rubidium@8985: belugas@6179: /* 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@6179: /* 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@6247: void UndrawMouseCursor() truelight@0: { truelight@0: if (_cursor.visible) { truelight@6937: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); truelight@0: _cursor.visible = false; truelight@6985: 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@7170: _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@6247: void DrawMouseCursor() truelight@0: { truelight@7408: #if defined(WINCE) truelight@7408: /* Don't ever draw the mouse for WinCE, as we work with a stylus */ truelight@7408: return; truelight@7408: #endif truelight@7408: truelight@6937: 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@6179: /* 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; rubidium@9670: x = _cursor.pos.x + _cursor.offs.x + _cursor.short_vehicle_offset; 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@6937: assert(blitter->BufferSize(w, h) < (int)sizeof(_cursor_backup)); truelight@0: belugas@6179: /* Make backup of stuff below cursor */ truelight@6985: 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@6179: /* Draw cursor on screen */ truelight@0: _cur_dpi = &_screen; rubidium@9670: DrawSprite(_cursor.sprite, _cursor.pal, _cursor.pos.x + _cursor.short_vehicle_offset, _cursor.pos.y); truelight@0: peter1138@7170: _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@9898: rubidium@9898: #ifdef ENABLE_NETWORK rubidium@9898: NetworkUndrawChatMessage(); rubidium@9898: #endif /* ENABLE_NETWORK */ truelight@0: truelight@6999: DrawOverlappedWindowForAll(left, top, right, bottom); truelight@0: peter1138@7170: _video_driver->MakeDirty(left, top, right - left, bottom - top); truelight@0: } truelight@0: rubidium@7545: /*! rubidium@7545: * Repaints the rectangle blocks which are marked as 'dirty'. rubidium@7545: * rubidium@7545: * @see SetDirtyBlocks rubidium@7545: */ rubidium@6247: void DrawDirtyBlocks() truelight@0: { truelight@0: byte *b = _dirty_blocks; rubidium@8981: const int w = Align(_screen.width, DIRTY_BLOCK_WIDTH); rubidium@8981: 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@8981: int right = x + DIRTY_BLOCK_WIDTH; tron@2025: int bottom = y; tron@2025: byte *p = b; tron@2025: int h2; truelight@0: belugas@6179: /* First try coalescing downwards */ truelight@0: do { tron@2025: *p = 0; rubidium@8985: p += _dirty_bytes_per_line; rubidium@8981: bottom += DIRTY_BLOCK_HEIGHT; tron@2025: } while (bottom != h && *p != 0); truelight@0: belugas@6179: /* Try coalescing to the right too. */ rubidium@8981: 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@6179: /* Check if a full line of dirty flags is set. */ tron@2025: do { tron@2025: if (!*p2) goto no_more_coalesc; rubidium@8985: p2 += _dirty_bytes_per_line; tron@2025: } while (--h != 0); tron@2025: belugas@6179: /* Wohoo, can combine it one step to the right! belugas@6179: * Do that, and clear the bits. */ rubidium@8981: right += DIRTY_BLOCK_WIDTH; tron@2025: tron@2025: h = h2; tron@2025: p2 = p; tron@2025: do { tron@2025: *p2 = 0; rubidium@8985: 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@8981: } while (b++, (x += DIRTY_BLOCK_WIDTH) != w); rubidium@8985: } 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@7545: /*! rubidium@7545: * This function extends the internal _invalid_rect rectangle as it rubidium@7545: * now contains the rectangle defined by the given parameters. Note rubidium@7545: * the point (0,0) is top left. rubidium@7545: * rubidium@7545: * @param left The left edge of the rectangle rubidium@7545: * @param top The top edge of the rectangle rubidium@7545: * @param right The right edge of the rectangle rubidium@7545: * @param bottom The bottm edge of the rectangle rubidium@7545: * @see DrawDirtyBlocks rubidium@7545: * rubidium@7545: * @todo The name of the function should be called like @c AddDirtyBlock as rubidium@7545: * it neither set a dirty rect nor add several dirty rects although rubidium@7545: * the function name is in plural. (Progman) rubidium@7545: */ 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@8981: left /= DIRTY_BLOCK_WIDTH; rubidium@8981: top /= DIRTY_BLOCK_HEIGHT; truelight@0: rubidium@8985: b = _dirty_blocks + top * _dirty_bytes_per_line + left; truelight@0: rubidium@8981: width = ((right - 1) / DIRTY_BLOCK_WIDTH) - left + 1; rubidium@8981: 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@8985: b += _dirty_bytes_per_line; tron@2010: } while (--height != 0); truelight@0: } truelight@0: rubidium@7545: /*! rubidium@7545: * This function mark the whole screen as dirty. This results in repainting rubidium@7545: * the whole screen. Use this with care as this function will break the rubidium@7545: * idea about marking only parts of the screen as 'dirty'. rubidium@7545: */ rubidium@6247: 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@6937: Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); tron@4429: const DrawPixelInfo *o = _cur_dpi; truelight@0: truelight@6624: 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@6937: n->dst_ptr = blitter->MoveTo(o->dst_ptr, left, top); truelight@6878: 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@5668: 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: rubidium@10056: p = GetSprite(GB(cursor, 0, SPRITE_WIDTH), ST_NORMAL); truelight@0: cv->sprite = cursor; peter1138@5668: 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; rubidium@9670: cv->short_vehicle_offset = 0; truelight@0: } truelight@0: rubidium@6247: static void SwitchAnimatedCursor() truelight@0: { rubidium@6138: const AnimCursor *cur = _cursor.animate_cur; truelight@0: rubidium@6138: if (cur == NULL || cur->sprite == AnimCursor::LAST) cur = _cursor.animate_list; Darkvater@1914: rubidium@6138: SetCursorSprite(cur->sprite, _cursor.pal); truelight@0: rubidium@6138: _cursor.animate_timeout = cur->display_time; rubidium@6138: _cursor.animate_cur = cur + 1; truelight@0: } truelight@0: rubidium@6247: void CursorTick() tron@1093: { tron@2010: if (_cursor.animate_timeout != 0 && --_cursor.animate_timeout == 0) truelight@0: SwitchAnimatedCursor(); truelight@0: } truelight@0: peter1138@5668: void SetMouseCursor(SpriteID sprite, SpriteID pal) truelight@0: { belugas@6179: /* Turn off animation */ truelight@0: _cursor.animate_timeout = 0; belugas@6179: /* Set cursor */ peter1138@5668: SetCursorSprite(sprite, pal); truelight@0: } truelight@0: rubidium@6138: void SetAnimatedMouseCursor(const AnimCursor *table) truelight@0: { truelight@0: _cursor.animate_list = table; truelight@0: _cursor.animate_cur = NULL; peter1138@5668: _cursor.pal = PAL_NONE; truelight@0: SwitchAnimatedCursor(); truelight@0: } truelight@0: rubidium@7360: bool ChangeResInGame(int width, int height) truelight@193: { rubidium@7362: return (_screen.width == width && _screen.height == height) || _video_driver->ChangeResolution(width, height); truelight@0: } darkvater@298: belugas@8171: bool ToggleFullScreen(bool fs) rubidium@5217: { belugas@8171: bool result = _video_driver->ToggleFullscreen(fs); rubidium@5217: if (_fullscreen != fs && _num_resolutions == 0) { Darkvater@5380: DEBUG(driver, 0, "Could not find a suitable fullscreen resolution"); rubidium@5217: } belugas@8171: return result; rubidium@5217: } Darkvater@1829: smatz@9533: static int CDECL compare_res(const Dimension *pa, const Dimension *pb) darkvater@298: { smatz@9533: int x = pa->width - pb->width; Darkvater@1806: if (x != 0) return x; smatz@9533: return pa->height - pb->height; Darkvater@1806: } Darkvater@1806: Darkvater@1806: void SortResolutions(int count) Darkvater@1806: { smatz@9533: QSortT(_resolutions, count, &compare_res); darkvater@298: }