diff --git a/src/gfx.cpp b/src/gfx.cpp index acb0715058..81b43e79e9 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -20,6 +20,10 @@ #include "window_func.h" #include "newgrf_debug.h" #include "thread.h" +#include "widget_type.h" +#include "window_gui.h" +#include "framerate_type.h" +#include "transparency.h" #include "table/palettes.h" #include "table/string_colours.h" @@ -68,17 +72,16 @@ ZoomLevel _font_zoom; ///< Font Zoom level * * @ingroup dirty */ -static Rect _invalid_rect; static const byte *_colour_remap_ptr; static byte _string_colourremap[3]; ///< Recoloursprite for stringdrawing. The grf loader ensures that #ST_FONT sprites only use colours 0 to 2. static const uint DIRTY_BLOCK_HEIGHT = 8; static const uint DIRTY_BLOCK_WIDTH = 64; -static uint _dirty_bytes_per_line = 0; -static byte *_dirty_blocks = nullptr; extern uint _dirty_block_colour; +static bool _whole_screen_dirty = false; +static std::vector _dirty_blocks; /** * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen. @@ -1317,16 +1320,11 @@ void GetBroadestDigit(uint *front, uint *next, FontSize size) void ScreenSizeChanged() { - _dirty_bytes_per_line = CeilDiv(_screen.width, DIRTY_BLOCK_WIDTH); - _dirty_blocks = ReallocT(_dirty_blocks, _dirty_bytes_per_line * CeilDiv(_screen.height, DIRTY_BLOCK_HEIGHT)); + MarkWholeScreenDirty(); extern uint32 *_vp_map_line; _vp_map_line = ReallocT(_vp_map_line, _screen.width); - /* check the dirty rect */ - if (_invalid_rect.right >= _screen.width) _invalid_rect.right = _screen.width; - if (_invalid_rect.bottom >= _screen.height) _invalid_rect.bottom = _screen.height; - /* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */ _cursor.visible = false; } @@ -1424,6 +1422,62 @@ void RedrawScreenRect(int left, int top, int right, int bottom) VideoDriver::GetInstance()->MakeDirty(left, top, right - left, bottom - top); } +static std::vector _dirty_viewport_occlusions; +static const ViewPort *_dirty_viewport; +static NWidgetDisplay _dirty_viewport_disp_flags; + +static void DrawDirtyViewport(uint occlusion, int left, int top, int right, int bottom) +{ + for(; occlusion < _dirty_viewport_occlusions.size(); occlusion++) { + const Rect &occ = _dirty_viewport_occlusions[occlusion]; + if (right > occ.left && + bottom > occ.top && + left < occ.right && + top < occ.bottom) { + /* occlusion and draw rectangle intersect with each other */ + int x; + + if (left < (x = occ.left)) { + DrawDirtyViewport(occlusion + 1, left, top, x, bottom); + DrawDirtyViewport(occlusion, x, top, right, bottom); + return; + } + + if (right > (x = occ.right)) { + DrawDirtyViewport(occlusion, left, top, x, bottom); + DrawDirtyViewport(occlusion + 1, x, top, right, bottom); + return; + } + + if (top < (x = occ.top)) { + DrawDirtyViewport(occlusion + 1, left, top, right, x); + DrawDirtyViewport(occlusion, left, x, right, bottom); + return; + } + + if (bottom > (x = occ.bottom)) { + DrawDirtyViewport(occlusion, left, top, right, x); + DrawDirtyViewport(occlusion + 1, left, x, right, bottom); + return; + } + + return; + } + } + + if (_game_mode == GM_MENU) { + RedrawScreenRect(left, top, right, bottom); + } else { + extern void ViewportDrawChk(ViewPort *vp, int left, int top, int right, int bottom); + ViewportDrawChk(_dirty_viewport, left, top, right, bottom); + if (_dirty_viewport_disp_flags & (ND_SHADE_GREY | ND_SHADE_DIMMED)) { + GfxFillRect(left, top, right, bottom, + (_dirty_viewport_disp_flags & ND_SHADE_DIMMED) ? PALETTE_TO_TRANSPARENT : PALETTE_NEWSPAPER, FILLRECT_RECOLOUR); + } + VideoDriver::GetInstance()->MakeDirty(left, top, right - left, bottom - top); + } +} + /** * Repaints the rectangle blocks which are marked as 'dirty'. * @@ -1431,11 +1485,7 @@ void RedrawScreenRect(int left, int top, int right, int bottom) */ void DrawDirtyBlocks() { - byte *b = _dirty_blocks; - const int w = Align(_screen.width, DIRTY_BLOCK_WIDTH); - const int h = Align(_screen.height, DIRTY_BLOCK_HEIGHT); - int x; - int y; + static std::vector dirty_widgets; if (HasModalProgress()) { /* We are generating the world, so release our rights to the map and @@ -1460,79 +1510,285 @@ void DrawDirtyBlocks() if (_switch_mode != SM_NONE && !HasModalProgress()) return; } - y = 0; - do { - x = 0; - do { - if (*b != 0) { - int left; - int top; - int right = x + DIRTY_BLOCK_WIDTH; - int bottom = y; - byte *p = b; - int h2; + if (_whole_screen_dirty) { + RedrawScreenRect(0, 0, _screen.width, _screen.height); + Window *w; + FOR_ALL_WINDOWS_FROM_BACK(w) { + w->flags &= ~(WF_DIRTY | WF_WIDGETS_DIRTY); + } + _whole_screen_dirty = false; + } else { + bool cleared_overlays = false; + auto clear_overlays = [&]() { + if (cleared_overlays) return; + if (_cursor.visible) UndrawMouseCursor(); + if (_networking) NetworkUndrawChatMessage(); + cleared_overlays = true; + }; - /* First try coalescing downwards */ - do { - *p = 0; - p += _dirty_bytes_per_line; - bottom += DIRTY_BLOCK_HEIGHT; - } while (bottom != h && *p != 0); + DrawPixelInfo *old_dpi = _cur_dpi; + DrawPixelInfo bk; + _cur_dpi = &bk; - /* Try coalescing to the right too. */ - h2 = (bottom - y) / DIRTY_BLOCK_HEIGHT; - assert(h2 > 0); - p = b; + extern void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom, bool gfx_dirty); - while (right != w) { - byte *p2 = ++p; - int h = h2; - /* Check if a full line of dirty flags is set. */ - do { - if (!*p2) goto no_more_coalesc; - p2 += _dirty_bytes_per_line; - } while (--h != 0); + Window *w; + FOR_ALL_WINDOWS_FROM_BACK(w) { + if (!MayBeShown(w)) continue; - /* Wohoo, can combine it one step to the right! - * Do that, and clear the bits. */ - right += DIRTY_BLOCK_WIDTH; + if (w->viewport != nullptr) w->viewport->is_drawn = false; - h = h2; - p2 = p; - do { - *p2 = 0; - p2 += _dirty_bytes_per_line; - } while (--h != 0); + if (w->flags & WF_DIRTY) { + clear_overlays(); + DrawOverlappedWindow(w, max(0, w->left), max(0, w->top), min(_screen.width, w->left + w->width), min(_screen.height, w->top + w->height), true); + w->flags &= ~(WF_DIRTY | WF_WIDGETS_DIRTY); + } else if (w->flags & WF_WIDGETS_DIRTY) { + if (w->nested_root != nullptr) { + clear_overlays(); + w->nested_root->FillDirtyWidgets(dirty_widgets); + for (NWidgetBase *widget : dirty_widgets) { + DrawOverlappedWindow(w, max(0, w->left + widget->pos_x), max(0, w->top + widget->pos_y), min(_screen.width, w->left + widget->pos_x + widget->current_x), min(_screen.height, w->top + widget->pos_y + widget->current_y), true); + } + dirty_widgets.clear(); } - no_more_coalesc: - - left = x; - top = y; - - if (left < _invalid_rect.left ) left = _invalid_rect.left; - if (top < _invalid_rect.top ) top = _invalid_rect.top; - if (right > _invalid_rect.right ) right = _invalid_rect.right; - if (bottom > _invalid_rect.bottom) bottom = _invalid_rect.bottom; - - if (left < right && top < bottom) { - RedrawScreenRect(left, top, right, bottom); - } - + w->flags &= ~WF_WIDGETS_DIRTY; } - } while (b++, (x += DIRTY_BLOCK_WIDTH) != w); - } while (b += -(int)(w / DIRTY_BLOCK_WIDTH) + _dirty_bytes_per_line, (y += DIRTY_BLOCK_HEIGHT) != h); + if (w->viewport != nullptr) { + ViewPort *vp = w->viewport; + if (vp->is_drawn) { + vp->ClearDirty(); + } else if (vp->is_dirty) { + clear_overlays(); + PerformanceAccumulator framerate(PFE_DRAWWORLD); + _cur_dpi->left = 0; + _cur_dpi->top = 0; + _cur_dpi->width = _screen.width; + _cur_dpi->height = _screen.height; + _cur_dpi->pitch = _screen.pitch; + _cur_dpi->dst_ptr = _screen.dst_ptr; + _cur_dpi->zoom = ZOOM_LVL_NORMAL; + + _dirty_viewport = vp; + _dirty_viewport_disp_flags = w->viewport_widget->disp_flags; + TransparencyOptionBits to_backup = _transparency_opt; + if (_dirty_viewport_disp_flags & ND_NO_TRANSPARENCY) { + _transparency_opt &= (1 << TO_SIGNS) | (1 << TO_LOADING); // Disable all transparency, except textual stuff + } + + { + int left = vp->left; + int top = vp->top; + int right = vp->left + vp->width; + int bottom = vp->top + vp->height; + _dirty_viewport_occlusions.clear(); + const Window *v; + FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) { + if (MayBeShown(v) && + right > v->left && + bottom > v->top && + left < v->left + v->width && + top < v->top + v->height) { + _dirty_viewport_occlusions.push_back({ v->left, v->top, v->left + v->width, v->top + v->height }); + } + } + for (const Rect &r : _dirty_blocks) { + if (right > r.left && + bottom > r.top && + left < r.right && + top < r.bottom) { + _dirty_viewport_occlusions.push_back({ r.left, r.top, r.right, r.bottom }); + } + } + } + + const uint grid_w = vp->dirty_blocks_per_row; + const uint grid_h = vp->dirty_blocks_per_column; + + uint pos = 0; + uint x = 0; + do { + uint y = 0; + do { + if (vp->dirty_blocks[pos]) { + uint left = x; + uint top = y; + uint right = x + 1; + uint bottom = y; + uint p = pos; + + /* First try coalescing downwards */ + do { + vp->dirty_blocks[p] = false; + p++; + bottom++; + } while (bottom != grid_h && vp->dirty_blocks[p]); + + /* Try coalescing to the right too. */ + uint block_h = (bottom - y); + p = pos; + + while (right != grid_w) { + uint p2 = (p += grid_h); + uint check_h = block_h; + /* Check if a full line of dirty flags is set. */ + do { + if (!vp->dirty_blocks[p2]) goto no_more_coalesc; + p2++; + } while (--check_h != 0); + + /* Wohoo, can combine it one step to the right! + * Do that, and clear the bits. */ + right++; + + check_h = block_h; + p2 = p; + do { + vp->dirty_blocks[p2] = false; + p2++; + } while (--check_h != 0); + } + no_more_coalesc: + + assert(_cur_dpi == &bk); + int draw_left = max(0, ((left == 0) ? 0 : vp->dirty_block_left_margin + (left << vp->GetDirtyBlockWidthShift())) + vp->left); + int draw_top = max(0, (top << vp->GetDirtyBlockHeightShift()) + vp->top); + int draw_right = min(_screen.width, min((right << vp->GetDirtyBlockWidthShift()) + vp->dirty_block_left_margin, vp->width) + vp->left); + int draw_bottom = min(_screen.height, min(bottom << vp->GetDirtyBlockHeightShift(), vp->height) + vp->top); + if (draw_left < draw_right && draw_top < draw_bottom) { + DrawDirtyViewport(0, draw_left, draw_top, draw_right, draw_bottom); + } + } + } while (pos++, ++y != grid_h); + } while (++x != grid_w); + + _transparency_opt = to_backup; + w->viewport->ClearDirty(); + } + } + } + + _cur_dpi = old_dpi; + + for (const Rect &r : _dirty_blocks) { + RedrawScreenRect(r.left, r.top, r.right, r.bottom); + } + } + + _dirty_blocks.clear(); ++_dirty_block_colour; - _invalid_rect.left = w; - _invalid_rect.top = h; - _invalid_rect.right = 0; - _invalid_rect.bottom = 0; +} + +void UnsetDirtyBlocks(int left, int top, int right, int bottom) +{ + if (_whole_screen_dirty) return; + + for (uint i = 0; i < _dirty_blocks.size(); i++) { + Rect &r = _dirty_blocks[i]; + if (left < r.right && + right > r.left && + top < r.bottom && + bottom > r.top) { + /* overlap of some sort */ + if (left <= r.left && + right >= r.right && + top <= r.top && + bottom >= r.bottom) { + /* dirty rect entirely in subtraction area */ + r = _dirty_blocks.back(); + _dirty_blocks.pop_back(); + i--; + continue; + } + if (r.left < left) { + Rect n = { left, r.top, r.right, r.bottom }; + r.right = left; + _dirty_blocks.push_back(n); + continue; + } + if (r.right > right) { + Rect n = { r.left, r.top, right, r.bottom }; + r.left = right; + _dirty_blocks.push_back(n); + continue; + } + if (r.top < top) { + Rect n = { r.left, top, r.right, r.bottom }; + r.bottom = top; + _dirty_blocks.push_back(n); + continue; + } + if (r.bottom > bottom) { + Rect n = { r.left, r.top, r.right, bottom }; + r.top = bottom; + _dirty_blocks.push_back(n); + continue; + } + } + } +} + +static void AddDirtyBlocks(uint start, int left, int top, int right, int bottom) +{ + if (bottom <= top || right <= left) return; + + for (; start < _dirty_blocks.size(); start++) { + Rect &r = _dirty_blocks[start]; + if (left <= r.right && + right >= r.left && + top <= r.bottom && + bottom >= r.top) { + /* overlap or contact of some sort */ + if (left >= r.left && + right <= r.right && + top >= r.top && + bottom <= r.bottom) { + /* entirely contained by existing */ + return; + } + if (left <= r.left && + right >= r.right && + top <= r.top && + bottom >= r.bottom) { + /* entirely contains existing */ + r = _dirty_blocks.back(); + _dirty_blocks.pop_back(); + start--; + continue; + } + if (left < r.left && right > r.left) { + int middle = r.left; + AddDirtyBlocks(start, left, top, middle, bottom); + AddDirtyBlocks(start, middle, top, right, bottom); + return; + } + if (right > r.right && left < r.right) { + int middle = r.right; + AddDirtyBlocks(start, left, top, middle, bottom); + AddDirtyBlocks(start, middle, top, right, bottom); + return; + } + + if (top < r.top && bottom > r.top) { + int middle = r.top; + AddDirtyBlocks(start, left, top, right, middle); + AddDirtyBlocks(start, left, middle, right, bottom); + return; + } + + if (bottom > r.bottom && top < r.bottom) { + int middle = r.bottom; + AddDirtyBlocks(start, left, top, right, middle); + AddDirtyBlocks(start, left, middle, right, bottom); + return; + } + } + } + _dirty_blocks.push_back({ left, top, right, bottom }); } /** - * This function extends the internal _invalid_rect rectangle as it - * now contains the rectangle defined by the given parameters. Note - * the point (0,0) is top left. + * Note the point (0,0) is top left. * * @param left The left edge of the rectangle * @param top The top edge of the rectangle @@ -1546,39 +1802,14 @@ void DrawDirtyBlocks() */ void SetDirtyBlocks(int left, int top, int right, int bottom) { - byte *b; - int width; - int height; + if (_whole_screen_dirty) return; if (left < 0) left = 0; if (top < 0) top = 0; if (right > _screen.width) right = _screen.width; if (bottom > _screen.height) bottom = _screen.height; - if (left >= right || top >= bottom) return; - - if (left < _invalid_rect.left ) _invalid_rect.left = left; - if (top < _invalid_rect.top ) _invalid_rect.top = top; - if (right > _invalid_rect.right ) _invalid_rect.right = right; - if (bottom > _invalid_rect.bottom) _invalid_rect.bottom = bottom; - - left /= DIRTY_BLOCK_WIDTH; - top /= DIRTY_BLOCK_HEIGHT; - - b = _dirty_blocks + top * _dirty_bytes_per_line + left; - - width = ((right - 1) / DIRTY_BLOCK_WIDTH) - left + 1; - height = ((bottom - 1) / DIRTY_BLOCK_HEIGHT) - top + 1; - - assert(width > 0 && height > 0); - - do { - int i = width; - - do b[--i] = 0xFF; while (i != 0); - - b += _dirty_bytes_per_line; - } while (--height != 0); + AddDirtyBlocks(0, left, top, right, bottom); } /** @@ -1589,7 +1820,7 @@ void SetDirtyBlocks(int left, int top, int right, int bottom) */ void MarkWholeScreenDirty() { - SetDirtyBlocks(0, 0, _screen.width, _screen.height); + _whole_screen_dirty = true; } /** diff --git a/src/gfx_func.h b/src/gfx_func.h index d6d8107e74..584403caae 100644 --- a/src/gfx_func.h +++ b/src/gfx_func.h @@ -21,7 +21,7 @@ * (this is btw. also possible if needed). This is used to avoid a * flickering of the screen by the video driver constantly repainting it. * - * This whole mechanism is controlled by an rectangle defined in #_invalid_rect. This + * This whole mechanism was controlled by an rectangle defined in #_invalid_rect. This * rectangle defines the area on the screen which must be repaint. If a new object * needs to be repainted this rectangle is extended to 'catch' the object on the * screen. At some point (which is normally uninteresting for patch writers) this @@ -32,7 +32,6 @@ * rectangle information. Then a new round begins by marking objects "dirty". * * @see VideoDriver::MakeDirty - * @see _invalid_rect * @see _screen */ @@ -134,6 +133,7 @@ const char *GetCharAtPosition(const char *str, int x, FontSize start_fontsize = void DrawDirtyBlocks(); void SetDirtyBlocks(int left, int top, int right, int bottom); +void UnsetDirtyBlocks(int left, int top, int right, int bottom); void MarkWholeScreenDirty(); void GfxInitPalettes(); diff --git a/src/linkgraph/linkgraph_gui.h b/src/linkgraph/linkgraph_gui.h index 69a0df0ace..0c06250cc5 100644 --- a/src/linkgraph/linkgraph_gui.h +++ b/src/linkgraph/linkgraph_gui.h @@ -63,7 +63,7 @@ public: * @param company_mask Bitmask of companies to be shown. * @param scale Desired thickness of lines and size of station dots. */ - LinkGraphOverlay(const Window *w, uint wid, CargoTypes cargo_mask, uint32 company_mask, uint scale) : + LinkGraphOverlay(Window *w, uint wid, CargoTypes cargo_mask, uint32 company_mask, uint scale) : window(w), widget_id(wid), cargo_mask(cargo_mask), company_mask(company_mask), scale(scale) {} @@ -83,7 +83,7 @@ public: uint32 GetCompanyMask() { return this->company_mask; } protected: - const Window *window; ///< Window to be drawn into. + Window *window; ///< Window to be drawn into. const uint widget_id; ///< ID of Widget in Window to be drawn to. CargoTypes cargo_mask; ///< Bitmask of cargos to be displayed. uint32 company_mask; ///< Bitmask of companies to be displayed. diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 23f1e9f865..292d9b624d 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -149,6 +149,7 @@ bool DoZoomInOutWindow(ZoomStateChange how, Window *w) if (vp != nullptr) { // the vp can be null when how == ZOOM_NONE vp->virtual_left = w->viewport->scrollpos_x; vp->virtual_top = w->viewport->scrollpos_y; + UpdateViewportSizeZoom(vp); } /* Update the windows that have zoom-buttons to perhaps disable their buttons */ w->InvalidateData(); @@ -183,6 +184,7 @@ void FixTitleGameZoom() vp->zoom = _gui_zoom; vp->virtual_width = ScaleByZoom(vp->width, vp->zoom); vp->virtual_height = ScaleByZoom(vp->height, vp->zoom); + UpdateViewportSizeZoom(vp); } static const struct NWidgetPart _nested_main_window_widgets[] = { @@ -613,5 +615,4 @@ void GameSizeChanged() _cur_resolution.height = _screen.height; ScreenSizeChanged(); RelocateAllWindows(_screen.width, _screen.height); - MarkWholeScreenDirty(); } diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 0494261b25..376bd0aa1f 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -206,6 +206,19 @@ public: return nullptr; } + void FillDirtyWidgets(std::vector &dirty_widgets) override + { + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + int i = 0; + for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) { + if (!this->visible[i++]) continue; + child_wid->FillDirtyWidgets(dirty_widgets); + } + } + } + /** * Checks whether the given widget is actually visible. * @param widget the widget to check for visibility diff --git a/src/newgrf_gui.cpp b/src/newgrf_gui.cpp index 738782c6d6..450a9adb9e 100644 --- a/src/newgrf_gui.cpp +++ b/src/newgrf_gui.cpp @@ -1781,6 +1781,17 @@ public: this->acs->Draw(w); this->inf->Draw(w); } + + void FillDirtyWidgets(std::vector &dirty_widgets) override + { + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + if (this->editable) this->avs->FillDirtyWidgets(dirty_widgets); + this->acs->FillDirtyWidgets(dirty_widgets); + this->inf->FillDirtyWidgets(dirty_widgets); + } + } }; const uint NWidgetNewGRFDisplay::INTER_LIST_SPACING = WD_RESIZEBOX_WIDTH + 1; diff --git a/src/screenshot.cpp b/src/screenshot.cpp index 7553d85af4..9daa4f746a 100644 --- a/src/screenshot.cpp +++ b/src/screenshot.cpp @@ -789,6 +789,7 @@ void SetupScreenshotViewport(ScreenshotType t, ViewPort *vp) break; } } + UpdateViewportSizeZoom(vp); } /** diff --git a/src/smallmap_gui.cpp b/src/smallmap_gui.cpp index 8972d1c902..0962e4e6ce 100644 --- a/src/smallmap_gui.cpp +++ b/src/smallmap_gui.cpp @@ -1735,6 +1735,15 @@ public: { for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) child_wid->Draw(w); } + + void FillDirtyWidgets(std::vector &dirty_widgets) override + { + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) child_wid->FillDirtyWidgets(dirty_widgets); + } + } }; /** Widget parts of the smallmap display. */ diff --git a/src/station_gui.cpp b/src/station_gui.cpp index bfe306e6f9..d6878bdfc3 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -126,7 +126,7 @@ static void FindStationsAroundSelection() * If it is needed actually make the window for redrawing. * @param w the window to check. */ -void CheckRedrawStationCoverage(const Window *w) +void CheckRedrawStationCoverage(Window *w) { /* Test if ctrl state changed */ static bool _last_ctrl_pressed; diff --git a/src/station_gui.h b/src/station_gui.h index ac2fa2fc32..a6aae8cef9 100644 --- a/src/station_gui.h +++ b/src/station_gui.h @@ -23,7 +23,7 @@ enum StationCoverageType { }; int DrawStationCoverageAreaText(int left, int right, int top, StationCoverageType sct, int rad, bool supplies); -void CheckRedrawStationCoverage(const Window *w); +void CheckRedrawStationCoverage(Window *w); void ShowSelectStationIfNeeded(const CommandContainer &cmd, TileArea ta); void ShowSelectWaypointIfNeeded(const CommandContainer &cmd, TileArea ta); diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp index 856a8dd886..733d432f1b 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -1514,6 +1514,21 @@ public: return nullptr; } + + void FillDirtyWidgets(std::vector &dirty_widgets) override + { + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) { + if (child_wid->type == NWID_SPACER) continue; + if (!this->visible[((NWidgetCore*)child_wid)->index]) continue; + + child_wid->FillDirtyWidgets(dirty_widgets); + } + } + } + /** * Get the arrangement of the buttons for the toolbar. * @param width the new width of the toolbar. diff --git a/src/viewport.cpp b/src/viewport.cpp index c4d6ea0a63..1f24106dfe 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -258,7 +258,7 @@ struct ViewportDrawer { Point foundation_offset[FOUNDATION_PART_END]; ///< Pixel offset for ground sprites on the foundations. }; -static void MarkViewportDirty(const ViewPort * const vp, int left, int top, int right, int bottom); +static void MarkViewportDirty(ViewPort * const vp, int left, int top, int right, int bottom); static void MarkRouteStepDirty(RouteStepsMap::const_iterator cit); static void MarkRouteStepDirty(const TileIndex tile, uint order_nr); @@ -372,6 +372,8 @@ void InitializeWindowViewport(Window *w, int x, int y, vp->map_type = VPMT_BEGIN; + UpdateViewportSizeZoom(vp); + Point pt; if (follow_flags & 0x80000000) { @@ -575,6 +577,15 @@ static void DoSetViewportPosition(const Window *w, const int left, const int top } } +inline void UpdateViewportDirtyBlockLeftMargin(ViewPort *vp) +{ + if (vp->zoom >= ZOOM_LVL_DRAW_MAP) { + vp->dirty_block_left_margin = 0; + } else { + vp->dirty_block_left_margin = UnScaleByZoomLower((-vp->virtual_left) & 127, vp->zoom); + } +} + static void SetViewportPosition(Window *w, int x, int y, bool force_update_overlay) { ViewPort *vp = w->viewport; @@ -585,6 +596,7 @@ static void SetViewportPosition(Window *w, int x, int y, bool force_update_overl vp->virtual_left = x; vp->virtual_top = y; + UpdateViewportDirtyBlockLeftMargin(vp); if (force_update_overlay || IsViewportOverlayOutsideCachedRegion(w)) RebuildViewportOverlay(w, true); @@ -1479,8 +1491,8 @@ static void ViewportAddLandscape() * - Right column is column of upper_right (rounded up) and one column to the right. * Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement. */ - int left_column = (upper_left.y - upper_left.x) / (int)TILE_SIZE - 2; - int right_column = (upper_right.y - upper_right.x) / (int)TILE_SIZE + 2; + int left_column = DivTowardsNegativeInf(upper_left.y - upper_left.x, (int)TILE_SIZE) - 1; + int right_column = DivTowardsPositiveInf(upper_right.y - upper_right.x, (int)TILE_SIZE) + 1; int potential_bridge_height = ZOOM_LVL_BASE * TILE_HEIGHT * _settings_game.construction.max_bridge_height; @@ -1488,7 +1500,7 @@ static void ViewportAddLandscape() * The first row that could possibly be visible is the row above upper_left (if it is at height 0). * Due to integer-division not rounding down for negative numbers, we need another decrement. */ - int row = (upper_left.x + upper_left.y) / (int)TILE_SIZE - 2; + int row = DivTowardsNegativeInf(upper_left.y + upper_left.x, (int)TILE_SIZE) - 1; bool last_row = false; for (; !last_row; row++) { last_row = true; @@ -3003,8 +3015,8 @@ void ViewportDoDraw(const ViewPort *vp, int left, int top, int right, int bottom _vd.dpi.pitch = old_dpi->pitch; _vd.last_child = nullptr; - int x = UnScaleByZoom(_vd.dpi.left - (vp->virtual_left & mask), vp->zoom) + vp->left; - int y = UnScaleByZoom(_vd.dpi.top - (vp->virtual_top & mask), vp->zoom) + vp->top; + int x = UnScaleByZoomLower(_vd.dpi.left - (vp->virtual_left & mask), vp->zoom) + vp->left; + int y = UnScaleByZoomLower(_vd.dpi.top - (vp->virtual_top & mask), vp->zoom) + vp->top; _vd.dpi.dst_ptr = BlitterFactory::GetCurrentBlitter()->MoveTo(old_dpi->dst_ptr, x - old_dpi->left, y - old_dpi->top); @@ -3098,7 +3110,7 @@ void ViewportDoDraw(const ViewPort *vp, int left, int top, int right, int bottom * Make sure we don't draw a too big area at a time. * If we do, the sprite sorter will run into major performance problems and the sprite memory may overflow. */ -static void ViewportDrawChk(const ViewPort *vp, int left, int top, int right, int bottom) +void ViewportDrawChk(const ViewPort *vp, int left, int top, int right, int bottom) { if ((vp->zoom < ZOOM_LVL_DRAW_MAP) && ((int64)ScaleByZoom(bottom - top, vp->zoom) * (int64)ScaleByZoom(right - left, vp->zoom) > (int64)(1000000 * ZOOM_LVL_BASE * ZOOM_LVL_BASE))) { if ((bottom - top) > (right - left)) { @@ -3120,7 +3132,7 @@ static void ViewportDrawChk(const ViewPort *vp, int left, int top, int right, in } } -static inline void ViewportDraw(const ViewPort *vp, int left, int top, int right, int bottom) +static inline void ViewportDraw(ViewPort *vp, int left, int top, int right, int bottom) { if (right <= vp->left || bottom <= vp->top) return; @@ -3134,6 +3146,8 @@ static inline void ViewportDraw(const ViewPort *vp, int left, int top, int right if (top < vp->top) top = vp->top; if (bottom > vp->top + vp->height) bottom = vp->top + vp->height; + vp->is_drawn = true; + ViewportDrawChk(vp, left, top, right, bottom); } @@ -3230,6 +3244,15 @@ void UpdateViewportPosition(Window *w) } } +void UpdateViewportSizeZoom(ViewPort *vp) +{ + vp->dirty_blocks_per_column = CeilDiv(vp->height, vp->GetDirtyBlockHeight()); + vp->dirty_blocks_per_row = CeilDiv(vp->width, vp->GetDirtyBlockWidth()); + uint size = vp->dirty_blocks_per_row * vp->dirty_blocks_per_column; + vp->dirty_blocks.assign(size, false); + UpdateViewportDirtyBlockLeftMargin(vp); +} + void UpdateActiveScrollingViewport(Window *w) { if (w && (!_settings_client.gui.show_scrolling_viewport_on_map || w->viewport->zoom >= ZOOM_LVL_DRAW_MAP)) w = nullptr; @@ -3283,7 +3306,7 @@ void UpdateActiveScrollingViewport(Window *w) * @param bottom Bottom edge of area to repaint * @ingroup dirty */ -static void MarkViewportDirty(const ViewPort * const vp, int left, int top, int right, int bottom) +static void MarkViewportDirty(ViewPort * const vp, int left, int top, int right, int bottom) { /* Rounding wrt. zoom-out level */ right += (1 << vp->zoom) - 1; @@ -3305,12 +3328,21 @@ static void MarkViewportDirty(const ViewPort * const vp, int left, int top, int if (top >= vp->virtual_height) return; - SetDirtyBlocks( - UnScaleByZoomLower(left, vp->zoom) + vp->left, - UnScaleByZoomLower(top, vp->zoom) + vp->top, - UnScaleByZoom(right, vp->zoom) + vp->left + 1, - UnScaleByZoom(bottom, vp->zoom) + vp->top + 1 - ); + uint x = max(0, UnScaleByZoomLower(left, vp->zoom) - vp->dirty_block_left_margin) >> vp->GetDirtyBlockWidthShift(); + uint y = UnScaleByZoomLower(top, vp->zoom) >> vp->GetDirtyBlockHeightShift(); + uint w = (max(0, UnScaleByZoomLower(right, vp->zoom) - 1 - vp->dirty_block_left_margin) >> vp->GetDirtyBlockWidthShift()) + 1 - x; + uint h = ((UnScaleByZoom(bottom, vp->zoom) - 1) >> vp->GetDirtyBlockHeightShift()) + 1 - y; + + uint column_skip = vp->dirty_blocks_per_column - h; + uint pos = (x * vp->dirty_blocks_per_column) + y; + for (uint i = 0; i < w; i++) { + for (uint j = 0; j < h; j++) { + vp->dirty_blocks[pos] = true; + pos++; + } + pos += column_skip; + } + vp->is_dirty = true; } /** @@ -3324,7 +3356,7 @@ static void MarkViewportDirty(const ViewPort * const vp, int left, int top, int */ void MarkAllViewportsDirty(int left, int top, int right, int bottom, const ZoomLevel mark_dirty_if_zoomlevel_is_below) { - for (const ViewPort * const vp : _viewport_window_cache) { + for (ViewPort * const vp : _viewport_window_cache) { if (vp->zoom >= mark_dirty_if_zoomlevel_is_below) continue; MarkViewportDirty(vp, left, top, right, bottom); } @@ -3341,7 +3373,7 @@ static void MarkRouteStepDirty(const TileIndex tile, uint order_nr) assert(tile != INVALID_TILE); const Point pt = RemapCoords2(TileX(tile) * TILE_SIZE + TILE_SIZE / 2, TileY(tile) * TILE_SIZE + TILE_SIZE / 2); const int char_height = GetCharacterHeight(FS_SMALL) + 1; - for (const ViewPort * const vp : _viewport_window_cache) { + for (ViewPort * const vp : _viewport_window_cache) { const int half_width = ScaleByZoom((_vp_route_step_width / 2) + 1, vp->zoom); const int height = ScaleByZoom(_vp_route_step_height_top + char_height * order_nr + _vp_route_step_height_bottom, vp->zoom); MarkViewportDirty(vp, pt.x - half_width, pt.y - height, pt.x + half_width, pt.y); @@ -3370,7 +3402,7 @@ void MarkAllViewportMapsDirty(int left, int top, int right, int bottom) { Window *w; FOR_ALL_WINDOWS_FROM_BACK(w) { - const ViewPort *vp = w->viewport; + ViewPort *vp = w->viewport; if (vp != nullptr && vp->zoom >= ZOOM_LVL_DRAW_MAP) { assert(vp->width != 0); MarkViewportDirty(vp, left, top, right, bottom); diff --git a/src/viewport_func.h b/src/viewport_func.h index d42d532ad5..bf134b119b 100644 --- a/src/viewport_func.h +++ b/src/viewport_func.h @@ -29,6 +29,7 @@ ViewPort *IsPtInWindowViewport(const Window *w, int x, int y); Point TranslateXYToTileCoord(const ViewPort *vp, int x, int y, bool clamp_to_map = true); Point GetTileBelowCursor(); void UpdateViewportPosition(Window *w); +void UpdateViewportSizeZoom(ViewPort *vp); void MarkAllViewportsDirty(int left, int top, int right, int bottom, const ZoomLevel mark_dirty_if_zoomlevel_is_below = ZOOM_LVL_END); void MarkAllViewportMapsDirty(int left, int top, int right, int bottom); diff --git a/src/viewport_type.h b/src/viewport_type.h index d31d5dd328..6209de1068 100644 --- a/src/viewport_type.h +++ b/src/viewport_type.h @@ -14,6 +14,8 @@ #include "strings_type.h" #include "table/strings.h" +#include + class LinkGraphOverlay; enum ViewportMapType { @@ -45,6 +47,35 @@ struct ViewPort { ViewportMapType map_type; ///< Rendering type LinkGraphOverlay *overlay; + + std::vector dirty_blocks; + uint dirty_blocks_per_column; + uint dirty_blocks_per_row; + uint8 dirty_block_left_margin; + bool is_dirty = false; + bool is_drawn = false; + + uint GetDirtyBlockWidthShift() const { return this->GetDirtyBlockShift(); } + uint GetDirtyBlockHeightShift() const { return this->GetDirtyBlockShift(); } + uint GetDirtyBlockWidth() const { return 1 << this->GetDirtyBlockWidthShift(); } + uint GetDirtyBlockHeight() const { return 1 << this->GetDirtyBlockHeightShift(); } + + void ClearDirty() + { + if (this->is_dirty) { + this->dirty_blocks.assign(this->dirty_blocks.size(), false); + this->is_dirty = false; + } + this->is_drawn = false; + } + +private: + uint GetDirtyBlockShift() const + { + if (this->zoom >= ZOOM_LVL_DRAW_MAP) return 3; + if (this->zoom >= ZOOM_LVL_OUT_8X) return 4; + return 7 - this->zoom; + } }; /** Margins for the viewport sign */ diff --git a/src/widget.cpp b/src/widget.cpp index 1e83c6efbd..1608c8c26d 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -770,11 +770,10 @@ NWidgetBase::NWidgetBase(WidgetType tp) : ZeroedMemoryAllocator() * Mark the widget as 'dirty' (in need of repaint). * @param w Window owning the widget. */ -void NWidgetBase::SetDirty(const Window *w) const +void NWidgetBase::SetDirty(Window *w) { - int abs_left = w->left + this->pos_x; - int abs_top = w->top + this->pos_y; - SetDirtyBlocks(abs_left, abs_top, abs_left + this->current_x, abs_top + this->current_y); + this->base_flags |= WBF_DIRTY; + w->flags |= WF_DIRTY; } /** @@ -905,6 +904,11 @@ NWidgetCore *NWidgetCore::GetWidgetFromPos(int x, int y) return (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) ? this : nullptr; } +void NWidgetCore::FillDirtyWidgets(std::vector &dirty_widgets) +{ + if (this->base_flags & WBF_DIRTY) dirty_widgets.push_back(this); +} + /** * Constructor container baseclass. * @param tp Type of the container. @@ -1049,6 +1053,7 @@ void NWidgetStacked::FillNestedArray(NWidgetBase **array, uint length) void NWidgetStacked::Draw(const Window *w) { if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; if (this->shown_plane >= SZSP_BEGIN) return; int plane = 0; @@ -1076,6 +1081,21 @@ NWidgetCore *NWidgetStacked::GetWidgetFromPos(int x, int y) return nullptr; } +void NWidgetStacked::FillDirtyWidgets(std::vector &dirty_widgets) +{ + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + int plane = 0; + for (NWidgetBase *child_wid = this->head; child_wid != nullptr; plane++, child_wid = child_wid->next) { + if (plane == this->shown_plane) { + child_wid->FillDirtyWidgets(dirty_widgets); + return; + } + } + } +} + /** * Select which plane to show (for #NWID_SELECTION only). * @param plane Plane number to display. @@ -1109,6 +1129,7 @@ void NWidgetPIPContainer::SetPIP(uint8 pip_pre, uint8 pip_inter, uint8 pip_post) void NWidgetPIPContainer::Draw(const Window *w) { if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) { child_wid->Draw(w); } @@ -1125,6 +1146,17 @@ NWidgetCore *NWidgetPIPContainer::GetWidgetFromPos(int x, int y) return nullptr; } +void NWidgetPIPContainer::FillDirtyWidgets(std::vector &dirty_widgets) +{ + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) { + child_wid->FillDirtyWidgets(dirty_widgets); + } + } +} + /** Horizontal container widget. */ NWidgetHorizontal::NWidgetHorizontal(NWidContainerFlags flags) : NWidgetPIPContainer(NWID_HORIZONTAL, flags) { @@ -1456,7 +1488,7 @@ void NWidgetSpacer::Draw(const Window *w) /* Spacer widget is never visible. */ } -void NWidgetSpacer::SetDirty(const Window *w) const +void NWidgetSpacer::SetDirty(Window *w) { /* Spacer widget never need repainting. */ } @@ -1466,6 +1498,11 @@ NWidgetCore *NWidgetSpacer::GetWidgetFromPos(int x, int y) return nullptr; } +void NWidgetSpacer::FillDirtyWidgets(std::vector &dirty_widgets) +{ + /* Spacer widget never need repainting. */ +} + NWidgetMatrix::NWidgetMatrix() : NWidgetPIPContainer(NWID_MATRIX, NC_EQUALSIZE), index(-1), clicked(-1), count(-1) { } @@ -1623,9 +1660,17 @@ NWidgetCore *NWidgetMatrix::GetWidgetFromPos(int x, int y) return child->GetWidgetFromPos(x, y); } +void NWidgetMatrix::FillDirtyWidgets(std::vector &dirty_widgets) +{ + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } +} + /* virtual */ void NWidgetMatrix::Draw(const Window *w) { if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; /* Fill the background. */ GfxFillRect(this->pos_x, this->pos_y, this->pos_x + this->current_x - 1, this->pos_y + this->current_y - 1, _colour_gradient[this->colour & 0xF][5]); @@ -1831,6 +1876,8 @@ void NWidgetBackground::FillNestedArray(NWidgetBase **array, uint length) void NWidgetBackground::Draw(const Window *w) { if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; + if (this->current_x == 0 || this->current_y == 0) return; Rect r; @@ -1880,6 +1927,15 @@ NWidgetCore *NWidgetBackground::GetWidgetFromPos(int x, int y) return nwid; } +void NWidgetBackground::FillDirtyWidgets(std::vector &dirty_widgets) +{ + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + if (this->child != nullptr) this->child->FillDirtyWidgets(dirty_widgets); + } +} + NWidgetBase *NWidgetBackground::GetWidgetOfType(WidgetType tp) { NWidgetBase *nwid = nullptr; @@ -1906,6 +1962,8 @@ void NWidgetViewport::SetupSmallestSize(Window *w, bool init_array) void NWidgetViewport::Draw(const Window *w) { if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; + if (this->disp_flags & ND_NO_TRANSPARENCY) { TransparencyOptionBits to_backup = _transparency_opt; _transparency_opt &= (1 << TO_SIGNS) | (1 << TO_LOADING); // Disable all transparency, except textual stuff @@ -1931,6 +1989,7 @@ void NWidgetViewport::Draw(const Window *w) void NWidgetViewport::InitializeViewport(Window *w, uint32 follow_flags, ZoomLevel zoom) { InitializeWindowViewport(w, this->pos_x, this->pos_y, this->current_x, this->current_y, follow_flags, zoom); + w->viewport_widget = this; } /** @@ -1948,6 +2007,7 @@ void NWidgetViewport::UpdateViewportCoordinates(Window *w) vp->virtual_width = ScaleByZoom(vp->width, vp->zoom); vp->virtual_height = ScaleByZoom(vp->height, vp->zoom); + UpdateViewportSizeZoom(vp); } } @@ -2030,6 +2090,8 @@ void NWidgetScrollbar::SetupSmallestSize(Window *w, bool init_array) void NWidgetScrollbar::Draw(const Window *w) { if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; + if (this->current_x == 0 || this->current_y == 0) return; Rect r; @@ -2398,6 +2460,8 @@ void NWidgetLeaf::SetupSmallestSize(Window *w, bool init_array) void NWidgetLeaf::Draw(const Window *w) { if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; + if (this->current_x == 0 || this->current_y == 0) return; /* Setup a clipping rectangle... */ diff --git a/src/widget_type.h b/src/widget_type.h index 465c9cb75b..4d9247f7f4 100644 --- a/src/widget_type.h +++ b/src/widget_type.h @@ -18,6 +18,8 @@ #include "gfx_type.h" #include "window_type.h" +#include + static const int WIDGET_LIST_END = -1; ///< indicate the end of widgets' list for vararg functions /** Bits of the #WWT_MATRIX widget data. */ @@ -42,7 +44,7 @@ enum ArrowWidgetValues { /** * Window widget types, nested widget types, and nested widget part types. */ -enum WidgetType { +enum WidgetType : uint8 { /* Window widget types. */ WWT_EMPTY, ///< Empty widget, place holder to reserve space in widget array @@ -106,6 +108,14 @@ enum WidgetType { NWID_PUSHBUTTON_DROPDOWN = NWID_BUTTON_DROPDOWN | WWB_PUSHBUTTON, }; +/** + * Base widget flags. + */ +enum WidgetBaseFlags : uint8 { + WBF_DIRTY = 1 << 0, ///< Widget is dirty. +}; +DECLARE_ENUM_AS_BIT_SET(WidgetBaseFlags) + /** Different forms of sizing nested widgets, using NWidgetBase::AssignSizePosition() */ enum SizingType { ST_SMALLEST, ///< Initialize nested widget tree to smallest size. Also updates \e current_x and \e current_y. @@ -157,9 +167,11 @@ public: inline uint GetVerticalStepSize(SizingType sizing) const; virtual void Draw(const Window *w) = 0; - virtual void SetDirty(const Window *w) const; + virtual void FillDirtyWidgets(std::vector &dirty_widgets) = 0; + virtual void SetDirty(Window *w); WidgetType type; ///< Type of the widget / nested widget. + WidgetBaseFlags base_flags; ///< Widget base flags uint fill_x; ///< Horizontal fill stepsize (from initial size, \c 0 means not resizable). uint fill_y; ///< Vertical fill stepsize (from initial size, \c 0 means not resizable). uint resize_x; ///< Horizontal resize step (\c 0 means not resizable). @@ -304,6 +316,7 @@ public: bool IsHighlighted() const override; TextColour GetHighlightColour() const override; void SetHighlighted(TextColour highlight_colour) override; + void FillDirtyWidgets(std::vector &dirty_widgets) override; NWidgetDisplay disp_flags; ///< Flags that affect display and interaction with the widget. Colours colour; ///< Colour of this widget. @@ -420,6 +433,7 @@ public: void Draw(const Window *w) override; NWidgetCore *GetWidgetFromPos(int x, int y) override; + void FillDirtyWidgets(std::vector &dirty_widgets) override; void SetDisplayedPlane(int plane); @@ -445,6 +459,7 @@ public: void Draw(const Window *w) override; NWidgetCore *GetWidgetFromPos(int x, int y) override; + void FillDirtyWidgets(std::vector &dirty_widgets) override; protected: NWidContainerFlags flags; ///< Flags of the container. @@ -511,6 +526,7 @@ public: void FillNestedArray(NWidgetBase **array, uint length) override; NWidgetCore *GetWidgetFromPos(int x, int y) override; + void FillDirtyWidgets(std::vector &dirty_widgets) override; void Draw(const Window *w) override; protected: int index; ///< If non-negative, index in the #Window::nested_array. @@ -540,8 +556,9 @@ public: void FillNestedArray(NWidgetBase **array, uint length) override; void Draw(const Window *w) override; - void SetDirty(const Window *w) const override; + void SetDirty(Window *w) override; NWidgetCore *GetWidgetFromPos(int x, int y) override; + void FillDirtyWidgets(std::vector &dirty_widgets) override; }; /** @@ -564,6 +581,7 @@ public: void Draw(const Window *w) override; NWidgetCore *GetWidgetFromPos(int x, int y) override; NWidgetBase *GetWidgetOfType(WidgetType tp) override; + void FillDirtyWidgets(std::vector &dirty_widgets) override; private: NWidgetPIPContainer *child; ///< Child widget. diff --git a/src/window.cpp b/src/window.cpp index 2130214b79..a6507eb0ed 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -607,7 +607,7 @@ void Window::RaiseButtons(bool autoraise) * Invalidate a widget, i.e. mark it as being changed and in need of redraw. * @param widget_index the widget to redraw. */ -void Window::SetWidgetDirty(byte widget_index) const +void Window::SetWidgetDirty(byte widget_index) { /* Sometimes this function is called before the window is even fully initialized */ if (this->nested_array == nullptr) return; @@ -876,27 +876,6 @@ static void DispatchMouseWheelEvent(Window *w, NWidgetCore *nwid, int wheel) } } -/** - * Returns whether a window may be shown or not. - * @param w The window to consider. - * @return True iff it may be shown, otherwise false. - */ -static bool MayBeShown(const Window *w) -{ - /* If we're not modal, everything is okay. */ - if (!HasModalProgress()) return true; - - switch (w->window_class) { - case WC_MAIN_WINDOW: ///< The background, i.e. the game. - case WC_MODAL_PROGRESS: ///< The actual progress window. - case WC_CONFIRM_POPUP_QUERY: ///< The abort window. - return true; - - default: - return false; - } -} - /** * Generate repaint events for the visible part of window w within the rectangle. * @@ -908,8 +887,9 @@ static bool MayBeShown(const Window *w) * @param top Top edge of the rectangle that should be repainted * @param right Right edge of the rectangle that should be repainted * @param bottom Bottom edge of the rectangle that should be repainted + * @param gfx_dirty Whether to mark gfx dirty */ -static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom) +void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom, bool gfx_dirty) { const Window *v; FOR_ALL_WINDOWS_FROM_BACK_FROM(v, w->z_front) { @@ -922,26 +902,26 @@ static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bo int x; if (left < (x = v->left)) { - DrawOverlappedWindow(w, left, top, x, bottom); - DrawOverlappedWindow(w, x, top, right, bottom); + DrawOverlappedWindow(w, left, top, x, bottom, gfx_dirty); + DrawOverlappedWindow(w, x, top, right, bottom, gfx_dirty); return; } if (right > (x = v->left + v->width)) { - DrawOverlappedWindow(w, left, top, x, bottom); - DrawOverlappedWindow(w, x, top, right, bottom); + DrawOverlappedWindow(w, left, top, x, bottom, gfx_dirty); + DrawOverlappedWindow(w, x, top, right, bottom, gfx_dirty); return; } if (top < (x = v->top)) { - DrawOverlappedWindow(w, left, top, right, x); - DrawOverlappedWindow(w, left, x, right, bottom); + DrawOverlappedWindow(w, left, top, right, x, gfx_dirty); + DrawOverlappedWindow(w, left, x, right, bottom, gfx_dirty); return; } if (bottom > (x = v->top + v->height)) { - DrawOverlappedWindow(w, left, top, right, x); - DrawOverlappedWindow(w, left, x, right, bottom); + DrawOverlappedWindow(w, left, top, right, x, gfx_dirty); + DrawOverlappedWindow(w, left, x, right, bottom, gfx_dirty); return; } @@ -959,6 +939,10 @@ static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bo dp->dst_ptr = BlitterFactory::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top); dp->zoom = ZOOM_LVL_NORMAL; w->OnPaint(); + if (gfx_dirty) { + VideoDriver::GetInstance()->MakeDirty(left, top, right - left, bottom - top); + UnsetDirtyBlocks(left, top, right, bottom); + } } /** @@ -984,7 +968,7 @@ void DrawOverlappedWindowForAll(int left, int top, int right, int bottom) left < w->left + w->width && top < w->top + w->height) { /* Window w intersects with the rectangle => needs repaint */ - DrawOverlappedWindow(w, max(left, w->left), max(top, w->top), min(right, w->left + w->width), min(bottom, w->top + w->height)); + DrawOverlappedWindow(w, max(left, w->left), max(top, w->top), min(right, w->left + w->width), min(bottom, w->top + w->height), false); } } _cur_dpi = old_dpi; @@ -994,7 +978,16 @@ void DrawOverlappedWindowForAll(int left, int top, int right, int bottom) * Mark entire window as dirty (in need of re-paint) * @ingroup dirty */ -void Window::SetDirty() const +void Window::SetDirty() +{ + this->flags |= WF_DIRTY; +} + +/** + * Mark entire window as dirty (in need of re-paint) + * @ingroup dirty + */ +void Window::SetDirtyAsBlocks() { SetDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height); } @@ -1007,7 +1000,7 @@ void Window::SetDirty() const */ void Window::ReInit(int rx, int ry) { - this->SetDirty(); // Mark whole current window as dirty. + this->SetDirtyAsBlocks(); // Mark whole current window as dirty. /* Save current size. */ int window_width = this->width; @@ -1120,7 +1113,7 @@ Window::~Window() if (this->viewport != nullptr) DeleteWindowViewport(this); - this->SetDirty(); + this->SetDirtyAsBlocks(); free(this->nested_array); // Contents is released through deletion of #nested_root. delete this->nested_root; @@ -2171,7 +2164,7 @@ void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen) if (new_bottom >= (int)_cur_resolution.height) delta_y -= Ceil(new_bottom - _cur_resolution.height, max(1U, w->nested_root->resize_y)); } - w->SetDirty(); + w->SetDirtyAsBlocks(); uint new_xinc = max(0, (w->nested_root->resize_x == 0) ? 0 : (int)(w->nested_root->current_x - w->nested_root->smallest_x) + delta_x); uint new_yinc = max(0, (w->nested_root->resize_y == 0) ? 0 : (int)(w->nested_root->current_y - w->nested_root->smallest_y) + delta_y); @@ -2236,7 +2229,7 @@ static EventState HandleWindowDragging() break; } - w->SetDirty(); + w->SetDirtyAsBlocks(); int x = _cursor.pos.x + _drag_delta.x; int y = _cursor.pos.y + _drag_delta.y; @@ -2371,7 +2364,7 @@ static EventState HandleWindowDragging() _drag_delta.y += y; if ((w->flags & WF_SIZING_LEFT) && x != 0) { _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position. - w->SetDirty(); + w->SetDirtyAsBlocks(); w->left -= x; // If dragging left edge, move left window edge in opposite direction by the same amount. /* ResizeWindow() below ensures marking new position as dirty. */ } else { @@ -3282,7 +3275,7 @@ void UpdateWindows() */ void SetWindowDirty(WindowClass cls, WindowNumber number) { - const Window *w; + Window *w; FOR_ALL_WINDOWS_FROM_BACK(w) { if (w->window_class == cls && w->window_number == number) w->SetDirty(); } @@ -3296,7 +3289,7 @@ void SetWindowDirty(WindowClass cls, WindowNumber number) */ void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index) { - const Window *w; + Window *w; FOR_ALL_WINDOWS_FROM_BACK(w) { if (w->window_class == cls && w->window_number == number) { w->SetWidgetDirty(widget_index); @@ -3504,8 +3497,6 @@ restart_search: goto restart_search; } } - - FOR_ALL_WINDOWS_FROM_BACK(w) w->SetDirty(); } /** Delete all always on-top windows to get an empty screen */ diff --git a/src/window_gui.h b/src/window_gui.h index 9b8ffa935e..a189715268 100644 --- a/src/window_gui.h +++ b/src/window_gui.h @@ -240,7 +240,7 @@ enum SortButtonState { /** * Window flags. */ -enum WindowFlags { +enum WindowFlags : uint16 { WF_TIMEOUT = 1 << 0, ///< Window timeout counter. WF_DRAGGING = 1 << 3, ///< Window is being dragged. @@ -252,6 +252,8 @@ enum WindowFlags { WF_WHITE_BORDER = 1 << 8, ///< Window white border counter bit mask. WF_HIGHLIGHTED = 1 << 9, ///< Window has a widget that has a highlight. WF_CENTERED = 1 << 10, ///< Window is centered and shall stay centered after ReInit. + WF_DIRTY = 1 << 11, ///< Whole window is dirty, and requires repainting. + WF_WIDGETS_DIRTY = 1 << 12, ///< One or more widgets are dirty, and require repainting. }; DECLARE_ENUM_AS_BIT_SET(WindowFlags) @@ -375,7 +377,8 @@ public: Owner owner; ///< The owner of the content shown in this window. Company colour is acquired from this variable. ViewportData *viewport; ///< Pointer to viewport data, if present. - const NWidgetCore *nested_focus; ///< Currently focused nested widget, or \c nullptr if no nested widget has focus. + NWidgetViewport *viewport_widget; ///< Pointer to viewport widget, if present. + NWidgetCore *nested_focus; ///< Currently focused nested widget, or \c nullptr if no nested widget has focus. SmallMap querystrings; ///< QueryString associated to WWT_EDITBOX widgets. NWidgetBase *nested_root; ///< Root of the nested tree. NWidgetBase **nested_array; ///< Array of pointers into the tree. Do not access directly, use #Window::GetWidget() instead. @@ -557,7 +560,7 @@ public: void RaiseButtons(bool autoraise = false); void CDECL SetWidgetsDisabledState(bool disab_stat, int widgets, ...); void CDECL SetWidgetsLoweredState(bool lowered_stat, int widgets, ...); - void SetWidgetDirty(byte widget_index) const; + void SetWidgetDirty(byte widget_index); void DrawWidgets() const; void DrawViewport() const; @@ -566,7 +569,8 @@ public: void DeleteChildWindows(WindowClass wc = WC_INVALID) const; - void SetDirty() const; + void SetDirty(); + void SetDirtyAsBlocks(); void ReInit(int rx = 0, int ry = 0); /** Is window shaded currently? */ @@ -1000,4 +1004,26 @@ void SetFocusedWindow(Window *w); void ScrollbarClickHandler(Window *w, NWidgetCore *nw, int x, int y); +/** + * Returns whether a window may be shown or not. + * @param w The window to consider. + * @return True iff it may be shown, otherwise false. + */ +inline bool MayBeShown(const Window *w) +{ + /* If we're not modal, everything is okay. */ + extern bool _in_modal_progress; + if (likely(!_in_modal_progress)) return true; + + switch (w->window_class) { + case WC_MAIN_WINDOW: ///< The background, i.e. the game. + case WC_MODAL_PROGRESS: ///< The actual progress window. + case WC_CONFIRM_POPUP_QUERY: ///< The abort window. + return true; + + default: + return false; + } +} + #endif /* WINDOW_GUI_H */