OpenTTD
widget.cpp
Go to the documentation of this file.
1 /* $Id: widget.cpp 27893 2017-08-13 18:38:42Z frosch $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8  */
9 
12 #include "stdafx.h"
13 #include "company_func.h"
14 #include "window_gui.h"
15 #include "viewport_func.h"
16 #include "zoom_func.h"
17 #include "strings_func.h"
18 #include "transparency.h"
19 #include "core/geometry_func.hpp"
20 #include "settings_type.h"
21 #include "querystring_gui.h"
22 
23 #include "table/sprites.h"
24 #include "table/strings.h"
25 #include "table/string_colours.h"
26 
27 #include "safeguards.h"
28 
38 static Point HandleScrollbarHittest(const Scrollbar *sb, int top, int bottom, bool horizontal)
39 {
40  /* Base for reversion */
41  int rev_base = top + bottom;
42  int button_size;
43  if (horizontal) {
44  button_size = NWidgetScrollbar::GetHorizontalDimension().width;
45  } else {
46  button_size = NWidgetScrollbar::GetVerticalDimension().height;
47  }
48  top += button_size; // top points to just below the up-button
49  bottom -= button_size; // bottom points to top of the down-button
50 
51  int height = (bottom - top);
52  int pos = sb->GetPosition();
53  int count = sb->GetCount();
54  int cap = sb->GetCapacity();
55 
56  if (count != 0) top += height * pos / count;
57 
58  if (cap > count) cap = count;
59  if (count != 0) bottom -= (count - pos - cap) * height / count;
60 
61  Point pt;
62  if (horizontal && _current_text_dir == TD_RTL) {
63  pt.x = rev_base - bottom;
64  pt.y = rev_base - top;
65  } else {
66  pt.x = top;
67  pt.y = bottom;
68  }
69  return pt;
70 }
71 
81 static void ScrollbarClickPositioning(Window *w, NWidgetScrollbar *sb, int x, int y, int mi, int ma)
82 {
83  int pos;
84  int button_size;
85  bool rtl = false;
86 
87  if (sb->type == NWID_HSCROLLBAR) {
88  pos = x;
89  rtl = _current_text_dir == TD_RTL;
90  button_size = NWidgetScrollbar::GetHorizontalDimension().width;
91  } else {
92  pos = y;
93  button_size = NWidgetScrollbar::GetVerticalDimension().height;
94  }
95  if (pos < mi + button_size) {
96  /* Pressing the upper button? */
98  if (_scroller_click_timeout <= 1) {
99  _scroller_click_timeout = 3;
100  sb->UpdatePosition(rtl ? 1 : -1);
101  }
102  w->scrolling_scrollbar = sb->index;
103  } else if (pos >= ma - button_size) {
104  /* Pressing the lower button? */
106 
107  if (_scroller_click_timeout <= 1) {
108  _scroller_click_timeout = 3;
109  sb->UpdatePosition(rtl ? -1 : 1);
110  }
111  w->scrolling_scrollbar = sb->index;
112  } else {
113  Point pt = HandleScrollbarHittest(sb, mi, ma, sb->type == NWID_HSCROLLBAR);
114 
115  if (pos < pt.x) {
116  sb->UpdatePosition(rtl ? 1 : -1, Scrollbar::SS_BIG);
117  } else if (pos > pt.y) {
118  sb->UpdatePosition(rtl ? -1 : 1, Scrollbar::SS_BIG);
119  } else {
120  _scrollbar_start_pos = pt.x - mi - button_size;
121  _scrollbar_size = ma - mi - button_size * 2;
122  w->scrolling_scrollbar = sb->index;
123  _cursorpos_drag_start = _cursor.pos;
124  }
125  }
126 
127  w->SetDirty();
128 }
129 
138 void ScrollbarClickHandler(Window *w, NWidgetCore *nw, int x, int y)
139 {
140  int mi, ma;
141 
142  if (nw->type == NWID_HSCROLLBAR) {
143  mi = nw->pos_x;
144  ma = nw->pos_x + nw->current_x;
145  } else {
146  mi = nw->pos_y;
147  ma = nw->pos_y + nw->current_y;
148  }
149  NWidgetScrollbar *scrollbar = dynamic_cast<NWidgetScrollbar*>(nw);
150  assert(scrollbar != NULL);
151  ScrollbarClickPositioning(w, scrollbar, x, y, mi, ma);
152 }
153 
162 int GetWidgetFromPos(const Window *w, int x, int y)
163 {
164  NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
165  return (nw != NULL) ? nw->index : -1;
166 }
167 
177 void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
178 {
179  assert(colour < COLOUR_END);
180 
181  uint dark = _colour_gradient[colour][3];
182  uint medium_dark = _colour_gradient[colour][5];
183  uint medium_light = _colour_gradient[colour][6];
184  uint light = _colour_gradient[colour][7];
185 
186  if (flags & FR_TRANSPARENT) {
187  GfxFillRect(left, top, right, bottom, PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOUR);
188  } else {
189  uint interior;
190 
191  if (flags & FR_LOWERED) {
192  GfxFillRect(left, top, left, bottom, dark);
193  GfxFillRect(left + WD_BEVEL_LEFT, top, right, top, dark);
194  GfxFillRect(right, top + WD_BEVEL_TOP, right, bottom - WD_BEVEL_BOTTOM, light);
195  GfxFillRect(left + WD_BEVEL_LEFT, bottom, right, bottom, light);
196  interior = (flags & FR_DARKENED ? medium_dark : medium_light);
197  } else {
198  GfxFillRect(left, top, left, bottom - WD_BEVEL_BOTTOM, light);
199  GfxFillRect(left + WD_BEVEL_LEFT, top, right - WD_BEVEL_RIGHT, top, light);
200  GfxFillRect(right, top, right, bottom - WD_BEVEL_BOTTOM, dark);
201  GfxFillRect(left, bottom, right, bottom, dark);
202  interior = medium_dark;
203  }
204  if (!(flags & FR_BORDERONLY)) {
205  GfxFillRect(left + WD_BEVEL_LEFT, top + WD_BEVEL_TOP, right - WD_BEVEL_RIGHT, bottom - WD_BEVEL_BOTTOM, interior);
206  }
207  }
208 }
209 
218 static inline void DrawImageButtons(const Rect &r, WidgetType type, Colours colour, bool clicked, SpriteID img)
219 {
220  assert(img != 0);
221  DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FR_LOWERED : FR_NONE);
222 
223  if ((type & WWT_MASK) == WWT_IMGBTN_2 && clicked) img++; // Show different image when clicked for #WWT_IMGBTN_2.
224  Dimension d = GetSpriteSize(img);
225  DrawSprite(img, PAL_NONE, CenterBounds(r.left, r.right, d.width) + clicked, CenterBounds(r.top, r.bottom, d.height) + clicked);
226 }
227 
235 static inline void DrawLabel(const Rect &r, WidgetType type, bool clicked, StringID str)
236 {
237  if (str == STR_NULL) return;
238  if ((type & WWT_MASK) == WWT_TEXTBTN_2 && clicked) str++;
240  int offset = max(0, ((int)(r.bottom - r.top + 1) - (int)d.height) / 2); // Offset for rendering the text vertically centered
241  DrawString(r.left + clicked, r.right + clicked, r.top + offset + clicked, str, TC_FROMSTRING, SA_HOR_CENTER);
242 }
243 
250 static inline void DrawText(const Rect &r, TextColour colour, StringID str)
251 {
253  int offset = max(0, ((int)(r.bottom - r.top + 1) - (int)d.height) / 2); // Offset for rendering the text vertically centered
254  if (str != STR_NULL) DrawString(r.left, r.right, r.top + offset, str, colour);
255 }
256 
263 static inline void DrawInset(const Rect &r, Colours colour, StringID str)
264 {
265  DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, FR_LOWERED | FR_DARKENED);
266  if (str != STR_NULL) DrawString(r.left + WD_INSET_LEFT, r.right - WD_INSET_RIGHT, r.top + WD_INSET_TOP, str);
267 }
268 
278 static inline void DrawMatrix(const Rect &r, Colours colour, bool clicked, uint16 data, uint resize_x, uint resize_y)
279 {
280  DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FR_LOWERED : FR_NONE);
281 
282  int num_columns = GB(data, MAT_COL_START, MAT_COL_BITS); // Lower 8 bits of the widget data: Number of columns in the matrix.
283  int column_width; // Width of a single column in the matrix.
284  if (num_columns == 0) {
285  column_width = resize_x;
286  num_columns = (r.right - r.left + 1) / column_width;
287  } else {
288  column_width = (r.right - r.left + 1) / num_columns;
289  }
290 
291  int num_rows = GB(data, MAT_ROW_START, MAT_ROW_BITS); // Upper 8 bits of the widget data: Number of rows in the matrix.
292  int row_height; // Height of a single row in the matrix.
293  if (num_rows == 0) {
294  row_height = resize_y;
295  num_rows = (r.bottom - r.top + 1) / row_height;
296  } else {
297  row_height = (r.bottom - r.top + 1) / num_rows;
298  }
299 
300  int col = _colour_gradient[colour & 0xF][6];
301 
302  int x = r.left;
303  for (int ctr = num_columns; ctr > 1; ctr--) {
304  x += column_width;
305  GfxFillRect(x, r.top + 1, x, r.bottom - 1, col);
306  }
307 
308  x = r.top;
309  for (int ctr = num_rows; ctr > 1; ctr--) {
310  x += row_height;
311  GfxFillRect(r.left + 1, x, r.right - 1, x, col);
312  }
313 
314  col = _colour_gradient[colour & 0xF][4];
315 
316  x = r.left - 1;
317  for (int ctr = num_columns; ctr > 1; ctr--) {
318  x += column_width;
319  GfxFillRect(x, r.top + 1, x, r.bottom - 1, col);
320  }
321 
322  x = r.top - 1;
323  for (int ctr = num_rows; ctr > 1; ctr--) {
324  x += row_height;
325  GfxFillRect(r.left + 1, x, r.right - 1, x, col);
326  }
327 }
328 
338 static inline void DrawVerticalScrollbar(const Rect &r, Colours colour, bool up_clicked, bool bar_dragged, bool down_clicked, const Scrollbar *scrollbar)
339 {
340  int centre = (r.right - r.left) / 2;
341  int height = NWidgetScrollbar::GetVerticalDimension().height;
342 
343  /* draw up/down buttons */
344  DrawFrameRect(r.left, r.top, r.right, r.top + height - 1, colour, (up_clicked) ? FR_LOWERED : FR_NONE);
345  DrawSprite(SPR_ARROW_UP, PAL_NONE, r.left + 1 + up_clicked, r.top + 1 + up_clicked);
346 
347  DrawFrameRect(r.left, r.bottom - (height - 1), r.right, r.bottom, colour, (down_clicked) ? FR_LOWERED : FR_NONE);
348  DrawSprite(SPR_ARROW_DOWN, PAL_NONE, r.left + 1 + down_clicked, r.bottom - (height - 2) + down_clicked);
349 
350  int c1 = _colour_gradient[colour & 0xF][3];
351  int c2 = _colour_gradient[colour & 0xF][7];
352 
353  /* draw "shaded" background */
354  GfxFillRect(r.left, r.top + height, r.right, r.bottom - height, c2);
355  GfxFillRect(r.left, r.top + height, r.right, r.bottom - height, c1, FILLRECT_CHECKER);
356 
357  /* draw shaded lines */
358  GfxFillRect(r.left + centre - 3, r.top + height, r.left + centre - 3, r.bottom - height, c1);
359  GfxFillRect(r.left + centre - 2, r.top + height, r.left + centre - 2, r.bottom - height, c2);
360  GfxFillRect(r.left + centre + 2, r.top + height, r.left + centre + 2, r.bottom - height, c1);
361  GfxFillRect(r.left + centre + 3, r.top + height, r.left + centre + 3, r.bottom - height, c2);
362 
363  Point pt = HandleScrollbarHittest(scrollbar, r.top, r.bottom, false);
364  DrawFrameRect(r.left, pt.x, r.right, pt.y, colour, bar_dragged ? FR_LOWERED : FR_NONE);
365 }
366 
376 static inline void DrawHorizontalScrollbar(const Rect &r, Colours colour, bool left_clicked, bool bar_dragged, bool right_clicked, const Scrollbar *scrollbar)
377 {
378  int centre = (r.bottom - r.top) / 2;
379  int width = NWidgetScrollbar::GetHorizontalDimension().width;
380 
381  DrawFrameRect(r.left, r.top, r.left + width - 1, r.bottom, colour, left_clicked ? FR_LOWERED : FR_NONE);
382  DrawSprite(SPR_ARROW_LEFT, PAL_NONE, r.left + 1 + left_clicked, r.top + 1 + left_clicked);
383 
384  DrawFrameRect(r.right - (width - 1), r.top, r.right, r.bottom, colour, right_clicked ? FR_LOWERED : FR_NONE);
385  DrawSprite(SPR_ARROW_RIGHT, PAL_NONE, r.right - (width - 2) + right_clicked, r.top + 1 + right_clicked);
386 
387  int c1 = _colour_gradient[colour & 0xF][3];
388  int c2 = _colour_gradient[colour & 0xF][7];
389 
390  /* draw "shaded" background */
391  GfxFillRect(r.left + width, r.top, r.right - width, r.bottom, c2);
392  GfxFillRect(r.left + width, r.top, r.right - width, r.bottom, c1, FILLRECT_CHECKER);
393 
394  /* draw shaded lines */
395  GfxFillRect(r.left + width, r.top + centre - 3, r.right - width, r.top + centre - 3, c1);
396  GfxFillRect(r.left + width, r.top + centre - 2, r.right - width, r.top + centre - 2, c2);
397  GfxFillRect(r.left + width, r.top + centre + 2, r.right - width, r.top + centre + 2, c1);
398  GfxFillRect(r.left + width, r.top + centre + 3, r.right - width, r.top + centre + 3, c2);
399 
400  /* draw actual scrollbar */
401  Point pt = HandleScrollbarHittest(scrollbar, r.left, r.right, true);
402  DrawFrameRect(pt.x, r.top, pt.y, r.bottom, colour, bar_dragged ? FR_LOWERED : FR_NONE);
403 }
404 
411 static inline void DrawFrame(const Rect &r, Colours colour, StringID str)
412 {
413  int x2 = r.left; // by default the left side is the left side of the widget
414 
415  if (str != STR_NULL) x2 = DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top, str);
416 
417  int c1 = _colour_gradient[colour][3];
418  int c2 = _colour_gradient[colour][7];
419 
420  /* If the frame has text, adjust the top bar to fit half-way through */
421  int dy1 = 4;
422  if (str != STR_NULL) dy1 = FONT_HEIGHT_NORMAL / 2 - 1;
423  int dy2 = dy1 + 1;
424 
425  if (_current_text_dir == TD_LTR) {
426  /* Line from upper left corner to start of text */
427  GfxFillRect(r.left, r.top + dy1, r.left + 4, r.top + dy1, c1);
428  GfxFillRect(r.left + 1, r.top + dy2, r.left + 4, r.top + dy2, c2);
429 
430  /* Line from end of text to upper right corner */
431  GfxFillRect(x2, r.top + dy1, r.right - 1, r.top + dy1, c1);
432  GfxFillRect(x2, r.top + dy2, r.right - 2, r.top + dy2, c2);
433  } else {
434  /* Line from upper left corner to start of text */
435  GfxFillRect(r.left, r.top + dy1, x2 - 2, r.top + dy1, c1);
436  GfxFillRect(r.left + 1, r.top + dy2, x2 - 2, r.top + dy2, c2);
437 
438  /* Line from end of text to upper right corner */
439  GfxFillRect(r.right - 5, r.top + dy1, r.right - 1, r.top + dy1, c1);
440  GfxFillRect(r.right - 5, r.top + dy2, r.right - 2, r.top + dy2, c2);
441  }
442 
443  /* Line from upper left corner to bottom left corner */
444  GfxFillRect(r.left, r.top + dy2, r.left, r.bottom - 1, c1);
445  GfxFillRect(r.left + 1, r.top + dy2 + 1, r.left + 1, r.bottom - 2, c2);
446 
447  /* Line from upper right corner to bottom right corner */
448  GfxFillRect(r.right - 1, r.top + dy2, r.right - 1, r.bottom - 2, c1);
449  GfxFillRect(r.right, r.top + dy1, r.right, r.bottom - 1, c2);
450 
451  GfxFillRect(r.left + 1, r.bottom - 1, r.right - 1, r.bottom - 1, c1);
452  GfxFillRect(r.left, r.bottom, r.right, r.bottom, c2);
453 }
454 
461 static inline void DrawShadeBox(const Rect &r, Colours colour, bool clicked)
462 {
463  DrawImageButtons(r, WWT_SHADEBOX, colour, clicked, clicked ? SPR_WINDOW_SHADE: SPR_WINDOW_UNSHADE);
464 }
465 
472 static inline void DrawStickyBox(const Rect &r, Colours colour, bool clicked)
473 {
474  DrawImageButtons(r, WWT_STICKYBOX, colour, clicked, clicked ? SPR_PIN_UP : SPR_PIN_DOWN);
475 }
476 
483 static inline void DrawDefSizeBox(const Rect &r, Colours colour, bool clicked)
484 {
485  DrawImageButtons(r, WWT_DEFSIZEBOX, colour, clicked, SPR_WINDOW_DEFSIZE);
486 }
487 
494 static inline void DrawDebugBox(const Rect &r, Colours colour, bool clicked)
495 {
496  DrawImageButtons(r, WWT_DEBUGBOX, colour, clicked, SPR_WINDOW_DEBUG);
497 }
498 
506 static inline void DrawResizeBox(const Rect &r, Colours colour, bool at_left, bool clicked)
507 {
508  DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FR_LOWERED : FR_NONE);
509  if (at_left) {
510  Dimension d = GetSpriteSize(SPR_WINDOW_RESIZE_LEFT);
511  DrawSprite(SPR_WINDOW_RESIZE_LEFT, PAL_NONE, r.left + WD_RESIZEBOX_RIGHT + clicked,
512  r.bottom + 1 - WD_RESIZEBOX_BOTTOM - d.height + clicked);
513  } else {
514  Dimension d = GetSpriteSize(SPR_WINDOW_RESIZE_RIGHT);
515  DrawSprite(SPR_WINDOW_RESIZE_RIGHT, PAL_NONE, r.right + 1 - WD_RESIZEBOX_RIGHT - d.width + clicked,
516  r.bottom + 1 - WD_RESIZEBOX_BOTTOM - d.height + clicked);
517  }
518 }
519 
525 static inline void DrawCloseBox(const Rect &r, Colours colour)
526 {
527  if (colour != COLOUR_WHITE) DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, FR_NONE);
528  Dimension d = GetSpriteSize(SPR_CLOSEBOX);
529  int s = UnScaleGUI(1); /* Offset to account for shadow of SPR_CLOSEBOX */
530  DrawSprite(SPR_CLOSEBOX, (colour != COLOUR_WHITE ? TC_BLACK : TC_SILVER) | (1 << PALETTE_TEXT_RECOLOUR), CenterBounds(r.left, r.right, d.width - s), CenterBounds(r.top, r.bottom, d.height - s));
531 }
532 
540 void DrawCaption(const Rect &r, Colours colour, Owner owner, StringID str)
541 {
542  bool company_owned = owner < MAX_COMPANIES;
543 
544  DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, FR_BORDERONLY);
545  DrawFrameRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, colour, company_owned ? FR_LOWERED | FR_DARKENED | FR_BORDERONLY : FR_LOWERED | FR_DARKENED);
546 
547  if (company_owned) {
548  GfxFillRect(r.left + 2, r.top + 2, r.right - 2, r.bottom - 2, _colour_gradient[_company_colours[owner]][4]);
549  }
550 
551  if (str != STR_NULL) {
553  int offset = max(0, ((int)(r.bottom - r.top + 1) - (int)d.height) / 2); // Offset for rendering the text vertically centered
554  DrawString(r.left + WD_CAPTIONTEXT_LEFT, r.right - WD_CAPTIONTEXT_RIGHT, r.top + offset, str, TC_FROMSTRING, SA_HOR_CENTER);
555  }
556 }
557 
568 static inline void DrawButtonDropdown(const Rect &r, Colours colour, bool clicked_button, bool clicked_dropdown, StringID str)
569 {
570  int text_offset = max(0, ((int)(r.bottom - r.top + 1) - FONT_HEIGHT_NORMAL) / 2); // Offset for rendering the text vertically centered
571 
572  int dd_width = NWidgetLeaf::dropdown_dimension.width;
573  int dd_height = NWidgetLeaf::dropdown_dimension.height;
574  int image_offset = max(0, ((int)(r.bottom - r.top + 1) - dd_height) / 2);
575 
576  if (_current_text_dir == TD_LTR) {
577  DrawFrameRect(r.left, r.top, r.right - dd_width, r.bottom, colour, clicked_button ? FR_LOWERED : FR_NONE);
578  DrawFrameRect(r.right + 1 - dd_width, r.top, r.right, r.bottom, colour, clicked_dropdown ? FR_LOWERED : FR_NONE);
579  DrawSprite(SPR_ARROW_DOWN, PAL_NONE, r.right - (dd_width - 2) + clicked_dropdown, r.top + image_offset + clicked_dropdown);
580  if (str != STR_NULL) DrawString(r.left + WD_DROPDOWNTEXT_LEFT + clicked_button, r.right - dd_width - WD_DROPDOWNTEXT_RIGHT + clicked_button, r.top + text_offset + clicked_button, str, TC_BLACK);
581  } else {
582  DrawFrameRect(r.left + dd_width, r.top, r.right, r.bottom, colour, clicked_button ? FR_LOWERED : FR_NONE);
583  DrawFrameRect(r.left, r.top, r.left + dd_width - 1, r.bottom, colour, clicked_dropdown ? FR_LOWERED : FR_NONE);
584  DrawSprite(SPR_ARROW_DOWN, PAL_NONE, r.left + 1 + clicked_dropdown, r.top + image_offset + clicked_dropdown);
585  if (str != STR_NULL) DrawString(r.left + dd_width + WD_DROPDOWNTEXT_LEFT + clicked_button, r.right - WD_DROPDOWNTEXT_RIGHT + clicked_button, r.top + text_offset + clicked_button, str, TC_BLACK);
586  }
587 }
588 
596 static inline void DrawDropdown(const Rect &r, Colours colour, bool clicked, StringID str)
597 {
598  DrawButtonDropdown(r, colour, false, clicked, str);
599 }
600 
605 {
606  this->nested_root->Draw(this);
607 
608  if (this->flags & WF_WHITE_BORDER) {
609  DrawFrameRect(0, 0, this->width - 1, this->height - 1, COLOUR_WHITE, FR_BORDERONLY);
610  }
611 
612  if (this->flags & WF_HIGHLIGHTED) {
613  extern bool _window_highlight_colour;
614  for (uint i = 0; i < this->nested_array_size; i++) {
615  const NWidgetBase *widget = this->GetWidget<NWidgetBase>(i);
616  if (widget == NULL || !widget->IsHighlighted()) continue;
617 
618  int left = widget->pos_x;
619  int top = widget->pos_y;
620  int right = left + widget->current_x - 1;
621  int bottom = top + widget->current_y - 1;
622 
623  int colour = _string_colourmap[_window_highlight_colour ? widget->GetHighlightColour() : TC_WHITE];
624 
625  GfxFillRect(left, top, left, bottom - WD_BEVEL_BOTTOM, colour);
626  GfxFillRect(left + WD_BEVEL_LEFT, top, right - WD_BEVEL_RIGHT, top, colour);
627  GfxFillRect(right, top, right, bottom - WD_BEVEL_BOTTOM, colour);
628  GfxFillRect(left, bottom, right, bottom, colour);
629  }
630  }
631 }
632 
638 void Window::DrawSortButtonState(int widget, SortButtonState state) const
639 {
640  if (state == SBS_OFF) return;
641 
642  assert(this->nested_array != NULL);
643  const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
644 
645  /* Sort button uses the same sprites as vertical scrollbar */
646  Dimension dim = NWidgetScrollbar::GetVerticalDimension();
647  int offset = this->IsWidgetLowered(widget) ? 1 : 0;
648  int x = offset + nwid->pos_x + (_current_text_dir == TD_LTR ? nwid->current_x - dim.width : 0);
649  int y = offset + nwid->pos_y + (nwid->current_y - dim.height) / 2;
650 
651  DrawSprite(state == SBS_DOWN ? SPR_ARROW_DOWN : SPR_ARROW_UP, PAL_NONE, x, y);
652 }
653 
659 {
660  return NWidgetScrollbar::GetVerticalDimension().width + 1;
661 }
662 
663 
722 {
723  this->type = tp;
724 }
725 
726 /* ~NWidgetContainer() takes care of #next and #prev data members. */
727 
775 void NWidgetBase::SetDirty(const Window *w) const
776 {
777  int abs_left = w->left + this->pos_x;
778  int abs_top = w->top + this->pos_y;
779  SetDirtyBlocks(abs_left, abs_top, abs_left + this->current_x, abs_top + this->current_y);
780 }
781 
796 {
797  return (this->type == tp) ? this : NULL;
798 }
799 
807 {
808  this->fill_x = fill_x;
809  this->fill_y = fill_y;
810 }
811 
817 void NWidgetResizeBase::SetMinimalSize(uint min_x, uint min_y)
818 {
819  this->min_x = max(this->min_x, min_x);
820  this->min_y = max(this->min_y, min_y);
821 }
822 
829 void NWidgetResizeBase::SetMinimalTextLines(uint8 min_lines, uint8 spacing, FontSize size)
830 {
831  this->min_y = min_lines * GetCharacterHeight(size) + spacing;
832 }
833 
839 void NWidgetResizeBase::SetFill(uint fill_x, uint fill_y)
840 {
841  this->fill_x = fill_x;
842  this->fill_y = fill_y;
843 }
844 
850 void NWidgetResizeBase::SetResize(uint resize_x, uint resize_y)
851 {
852  this->resize_x = resize_x;
853  this->resize_y = resize_y;
854 }
855 
856 void NWidgetResizeBase::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
857 {
858  this->StoreSizePosition(sizing, x, y, given_width, given_height);
859 }
860 
870 NWidgetCore::NWidgetCore(WidgetType tp, Colours colour, uint fill_x, uint fill_y, uint32 widget_data, StringID tool_tip) : NWidgetResizeBase(tp, fill_x, fill_y)
871 {
872  this->colour = colour;
873  this->index = -1;
874  this->widget_data = widget_data;
875  this->tool_tip = tool_tip;
876  this->scrollbar_index = -1;
877 }
878 
883 void NWidgetCore::SetIndex(int index)
884 {
885  assert(index >= 0);
886  this->index = index;
887 }
888 
894 void NWidgetCore::SetDataTip(uint32 widget_data, StringID tool_tip)
895 {
896  this->widget_data = widget_data;
897  this->tool_tip = tool_tip;
898 }
899 
900 void NWidgetCore::FillNestedArray(NWidgetBase **array, uint length)
901 {
902  if (this->index >= 0 && (uint)(this->index) < length) array[this->index] = this;
903 }
904 
906 {
907  return (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) ? this : NULL;
908 }
909 
915 {
916  this->head = NULL;
917  this->tail = NULL;
918 }
919 
920 NWidgetContainer::~NWidgetContainer()
921 {
922  while (this->head != NULL) {
923  NWidgetBase *wid = this->head->next;
924  delete this->head;
925  this->head = wid;
926  }
927  this->tail = NULL;
928 }
929 
931 {
932  if (this->type == tp) return this;
933  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
934  NWidgetBase *nwid = child_wid->GetWidgetOfType(tp);
935  if (nwid != NULL) return nwid;
936  }
937  return NULL;
938 }
939 
945 {
946  assert(wid->next == NULL && wid->prev == NULL);
947 
948  if (this->head == NULL) {
949  this->head = wid;
950  this->tail = wid;
951  } else {
952  assert(this->tail != NULL);
953  assert(this->tail->next == NULL);
954 
955  this->tail->next = wid;
956  wid->prev = this->tail;
957  this->tail = wid;
958  }
959 }
960 
961 void NWidgetContainer::FillNestedArray(NWidgetBase **array, uint length)
962 {
963  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
964  child_wid->FillNestedArray(array, length);
965  }
966 }
967 
972 {
973  this->index = -1;
974 }
975 
976 void NWidgetStacked::SetIndex(int index)
977 {
978  this->index = index;
979 }
980 
981 void NWidgetStacked::SetupSmallestSize(Window *w, bool init_array)
982 {
983  if (this->index >= 0 && init_array) { // Fill w->nested_array[]
984  assert(w->nested_array_size > (uint)this->index);
985  w->nested_array[this->index] = this;
986  }
987 
988  /* Zero size plane selected */
989  if (this->shown_plane >= SZSP_BEGIN) {
990  Dimension size = {0, 0};
991  Dimension padding = {0, 0};
992  Dimension fill = {(this->shown_plane == SZSP_HORIZONTAL), (this->shown_plane == SZSP_VERTICAL)};
993  Dimension resize = {(this->shown_plane == SZSP_HORIZONTAL), (this->shown_plane == SZSP_VERTICAL)};
994  /* Here we're primarily interested in the value of resize */
995  if (this->index >= 0) w->UpdateWidgetSize(this->index, &size, padding, &fill, &resize);
996 
997  this->smallest_x = size.width;
998  this->smallest_y = size.height;
999  this->fill_x = fill.width;
1000  this->fill_y = fill.height;
1001  this->resize_x = resize.width;
1002  this->resize_y = resize.height;
1003  return;
1004  }
1005 
1006  /* First sweep, recurse down and compute minimal size and filling. */
1007  this->smallest_x = 0;
1008  this->smallest_y = 0;
1009  this->fill_x = (this->head != NULL) ? 1 : 0;
1010  this->fill_y = (this->head != NULL) ? 1 : 0;
1011  this->resize_x = (this->head != NULL) ? 1 : 0;
1012  this->resize_y = (this->head != NULL) ? 1 : 0;
1013  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1014  child_wid->SetupSmallestSize(w, init_array);
1015 
1016  this->smallest_x = max(this->smallest_x, child_wid->smallest_x + child_wid->padding_left + child_wid->padding_right);
1017  this->smallest_y = max(this->smallest_y, child_wid->smallest_y + child_wid->padding_top + child_wid->padding_bottom);
1018  this->fill_x = LeastCommonMultiple(this->fill_x, child_wid->fill_x);
1019  this->fill_y = LeastCommonMultiple(this->fill_y, child_wid->fill_y);
1020  this->resize_x = LeastCommonMultiple(this->resize_x, child_wid->resize_x);
1021  this->resize_y = LeastCommonMultiple(this->resize_y, child_wid->resize_y);
1022  }
1023 }
1024 
1025 void NWidgetStacked::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
1026 {
1027  assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1028  this->StoreSizePosition(sizing, x, y, given_width, given_height);
1029 
1030  if (this->shown_plane >= SZSP_BEGIN) return;
1031 
1032  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1033  uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1034  uint child_width = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding_left - child_wid->padding_right, hor_step);
1035  uint child_pos_x = (rtl ? child_wid->padding_right : child_wid->padding_left);
1036 
1037  uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1038  uint child_height = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding_top - child_wid->padding_bottom, vert_step);
1039  uint child_pos_y = child_wid->padding_top;
1040 
1041  child_wid->AssignSizePosition(sizing, x + child_pos_x, y + child_pos_y, child_width, child_height, rtl);
1042  }
1043 }
1044 
1045 void NWidgetStacked::FillNestedArray(NWidgetBase **array, uint length)
1046 {
1047  if (this->index >= 0 && (uint)(this->index) < length) array[this->index] = this;
1048  NWidgetContainer::FillNestedArray(array, length);
1049 }
1050 
1052 {
1053  if (this->shown_plane >= SZSP_BEGIN) return;
1054 
1055  int plane = 0;
1056  for (NWidgetBase *child_wid = this->head; child_wid != NULL; plane++, child_wid = child_wid->next) {
1057  if (plane == this->shown_plane) {
1058  child_wid->Draw(w);
1059  return;
1060  }
1061  }
1062 
1063  NOT_REACHED();
1064 }
1065 
1067 {
1068  if (this->shown_plane >= SZSP_BEGIN) return NULL;
1069 
1070  if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
1071  int plane = 0;
1072  for (NWidgetBase *child_wid = this->head; child_wid != NULL; plane++, child_wid = child_wid->next) {
1073  if (plane == this->shown_plane) {
1074  return child_wid->GetWidgetFromPos(x, y);
1075  }
1076  }
1077  return NULL;
1078 }
1079 
1085 {
1086  this->shown_plane = plane;
1087 }
1088 
1089 NWidgetPIPContainer::NWidgetPIPContainer(WidgetType tp, NWidContainerFlags flags) : NWidgetContainer(tp)
1090 {
1091  this->flags = flags;
1092 }
1093 
1103 void NWidgetPIPContainer::SetPIP(uint8 pip_pre, uint8 pip_inter, uint8 pip_post)
1104 {
1105  this->pip_pre = pip_pre;
1106  this->pip_inter = pip_inter;
1107  this->pip_post = pip_post;
1108 }
1109 
1111 {
1112  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1113  child_wid->Draw(w);
1114  }
1115 }
1116 
1118 {
1119  if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
1120 
1121  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1122  NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
1123  if (nwid != NULL) return nwid;
1124  }
1125  return NULL;
1126 }
1127 
1130 {
1131 }
1132 
1134 {
1135  this->smallest_x = 0; // Sum of minimal size of all children.
1136  this->smallest_y = 0; // Biggest child.
1137  this->fill_x = 0; // smallest non-zero child widget fill step.
1138  this->fill_y = 1; // smallest common child fill step.
1139  this->resize_x = 0; // smallest non-zero child widget resize step.
1140  this->resize_y = 1; // smallest common child resize step.
1141 
1142  /* 1a. Forward call, collect biggest nested array index, and longest/widest child length. */
1143  uint longest = 0; // Longest child found.
1144  uint max_vert_fill = 0; // Biggest vertical fill step.
1145  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1146  child_wid->SetupSmallestSize(w, init_array);
1147  longest = max(longest, child_wid->smallest_x);
1148  max_vert_fill = max(max_vert_fill, child_wid->GetVerticalStepSize(ST_SMALLEST));
1149  this->smallest_y = max(this->smallest_y, child_wid->smallest_y + child_wid->padding_top + child_wid->padding_bottom);
1150  }
1151  /* 1b. Make the container higher if needed to accommodate all children nicely. */
1152  uint max_smallest = this->smallest_y + 3 * max_vert_fill; // Upper limit to computing smallest height.
1153  uint cur_height = this->smallest_y;
1154  for (;;) {
1155  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1156  uint step_size = child_wid->GetVerticalStepSize(ST_SMALLEST);
1157  uint child_height = child_wid->smallest_y + child_wid->padding_top + child_wid->padding_bottom;
1158  if (step_size > 1 && child_height < cur_height) { // Small step sizes or already fitting children are not interesting.
1159  uint remainder = (cur_height - child_height) % step_size;
1160  if (remainder > 0) { // Child did not fit entirely, widen the container.
1161  cur_height += step_size - remainder;
1162  assert(cur_height < max_smallest); // Safeguard against infinite height expansion.
1163  /* Remaining children will adapt to the new cur_height, thus speeding up the computation. */
1164  }
1165  }
1166  }
1167  if (this->smallest_y == cur_height) break;
1168  this->smallest_y = cur_height; // Smallest height got changed, try again.
1169  }
1170  /* 2. For containers that must maintain equal width, extend child minimal size. */
1171  if (this->flags & NC_EQUALSIZE) {
1172  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1173  if (child_wid->fill_x == 1) child_wid->smallest_x = longest;
1174  }
1175  }
1176  /* 3. Move PIP space to the children, compute smallest, fill, and resize values of the container. */
1177  if (this->head != NULL) this->head->padding_left += this->pip_pre;
1178  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1179  if (child_wid->next != NULL) {
1180  child_wid->padding_right += this->pip_inter;
1181  } else {
1182  child_wid->padding_right += this->pip_post;
1183  }
1184 
1185  this->smallest_x += child_wid->smallest_x + child_wid->padding_left + child_wid->padding_right;
1186  if (child_wid->fill_x > 0) {
1187  if (this->fill_x == 0 || this->fill_x > child_wid->fill_x) this->fill_x = child_wid->fill_x;
1188  }
1189  this->fill_y = LeastCommonMultiple(this->fill_y, child_wid->fill_y);
1190 
1191  if (child_wid->resize_x > 0) {
1192  if (this->resize_x == 0 || this->resize_x > child_wid->resize_x) this->resize_x = child_wid->resize_x;
1193  }
1194  this->resize_y = LeastCommonMultiple(this->resize_y, child_wid->resize_y);
1195  }
1196  /* We need to zero the PIP settings so we can re-initialize the tree. */
1197  this->pip_pre = this->pip_inter = this->pip_post = 0;
1198 }
1199 
1200 void NWidgetHorizontal::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
1201 {
1202  assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1203 
1204  /* Compute additional width given to us. */
1205  uint additional_length = given_width;
1206  if (sizing == ST_SMALLEST && (this->flags & NC_EQUALSIZE)) {
1207  /* For EQUALSIZE containers this does not sum to smallest_x during initialisation */
1208  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1209  additional_length -= child_wid->smallest_x + child_wid->padding_right + child_wid->padding_left;
1210  }
1211  } else {
1212  additional_length -= this->smallest_x;
1213  }
1214 
1215  this->StoreSizePosition(sizing, x, y, given_width, given_height);
1216 
1217  /* In principle, the additional horizontal space is distributed evenly over the available resizable children. Due to step sizes, this may not always be feasible.
1218  * To make resizing work as good as possible, first children with biggest step sizes are done. These may get less due to rounding down.
1219  * This additional space is then given to children with smaller step sizes. This will give a good result when resize steps of each child is a multiple
1220  * of the child with the smallest non-zero stepsize.
1221  *
1222  * Since child sizes are computed out of order, positions cannot be calculated until all sizes are known. That means it is not possible to compute the child
1223  * size and position, and directly call child->AssignSizePosition() with the computed values.
1224  * Instead, computed child widths and heights are stored in child->current_x and child->current_y values. That is allowed, since this method overwrites those values
1225  * then we call the child.
1226  */
1227 
1228  /* First loop: Find biggest stepsize, find number of children that want a piece of the pie, handle vertical size for all children,
1229  * handle horizontal size for non-resizing children.
1230  */
1231  int num_changing_childs = 0; // Number of children that can change size.
1232  uint biggest_stepsize = 0;
1233  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1234  uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1235  if (hor_step > 0) {
1236  num_changing_childs++;
1237  biggest_stepsize = max(biggest_stepsize, hor_step);
1238  } else {
1239  child_wid->current_x = child_wid->smallest_x;
1240  }
1241 
1242  uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1243  child_wid->current_y = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding_top - child_wid->padding_bottom, vert_step);
1244  }
1245 
1246  /* Second loop: Allocate the additional horizontal space over the resizing children, starting with the biggest resize steps. */
1247  while (biggest_stepsize > 0) {
1248  uint next_biggest_stepsize = 0;
1249  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1250  uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1251  if (hor_step > biggest_stepsize) continue; // Already done
1252  if (hor_step == biggest_stepsize) {
1253  uint increment = additional_length / num_changing_childs;
1254  num_changing_childs--;
1255  if (hor_step > 1) increment -= increment % hor_step;
1256  child_wid->current_x = child_wid->smallest_x + increment;
1257  additional_length -= increment;
1258  continue;
1259  }
1260  next_biggest_stepsize = max(next_biggest_stepsize, hor_step);
1261  }
1262  biggest_stepsize = next_biggest_stepsize;
1263  }
1264  assert(num_changing_childs == 0);
1265 
1266  /* Third loop: Compute position and call the child. */
1267  uint position = rtl ? this->current_x : 0; // Place to put next child relative to origin of the container.
1268  NWidgetBase *child_wid = this->head;
1269  while (child_wid != NULL) {
1270  uint child_width = child_wid->current_x;
1271  uint child_x = x + (rtl ? position - child_width - child_wid->padding_left : position + child_wid->padding_left);
1272  uint child_y = y + child_wid->padding_top;
1273 
1274  child_wid->AssignSizePosition(sizing, child_x, child_y, child_width, child_wid->current_y, rtl);
1275  uint padded_child_width = child_width + child_wid->padding_right + child_wid->padding_left;
1276  position = rtl ? position - padded_child_width : position + padded_child_width;
1277 
1278  child_wid = child_wid->next;
1279  }
1280 }
1281 
1284 {
1285  this->type = NWID_HORIZONTAL_LTR;
1286 }
1287 
1288 void NWidgetHorizontalLTR::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
1289 {
1290  NWidgetHorizontal::AssignSizePosition(sizing, x, y, given_width, given_height, false);
1291 }
1292 
1295 {
1296 }
1297 
1299 {
1300  this->smallest_x = 0; // Biggest child.
1301  this->smallest_y = 0; // Sum of minimal size of all children.
1302  this->fill_x = 1; // smallest common child fill step.
1303  this->fill_y = 0; // smallest non-zero child widget fill step.
1304  this->resize_x = 1; // smallest common child resize step.
1305  this->resize_y = 0; // smallest non-zero child widget resize step.
1306 
1307  /* 1a. Forward call, collect biggest nested array index, and longest/widest child length. */
1308  uint highest = 0; // Highest child found.
1309  uint max_hor_fill = 0; // Biggest horizontal fill step.
1310  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1311  child_wid->SetupSmallestSize(w, init_array);
1312  highest = max(highest, child_wid->smallest_y);
1313  max_hor_fill = max(max_hor_fill, child_wid->GetHorizontalStepSize(ST_SMALLEST));
1314  this->smallest_x = max(this->smallest_x, child_wid->smallest_x + child_wid->padding_left + child_wid->padding_right);
1315  }
1316  /* 1b. Make the container wider if needed to accommodate all children nicely. */
1317  uint max_smallest = this->smallest_x + 3 * max_hor_fill; // Upper limit to computing smallest height.
1318  uint cur_width = this->smallest_x;
1319  for (;;) {
1320  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1321  uint step_size = child_wid->GetHorizontalStepSize(ST_SMALLEST);
1322  uint child_width = child_wid->smallest_x + child_wid->padding_left + child_wid->padding_right;
1323  if (step_size > 1 && child_width < cur_width) { // Small step sizes or already fitting children are not interesting.
1324  uint remainder = (cur_width - child_width) % step_size;
1325  if (remainder > 0) { // Child did not fit entirely, widen the container.
1326  cur_width += step_size - remainder;
1327  assert(cur_width < max_smallest); // Safeguard against infinite width expansion.
1328  /* Remaining children will adapt to the new cur_width, thus speeding up the computation. */
1329  }
1330  }
1331  }
1332  if (this->smallest_x == cur_width) break;
1333  this->smallest_x = cur_width; // Smallest width got changed, try again.
1334  }
1335  /* 2. For containers that must maintain equal width, extend children minimal size. */
1336  if (this->flags & NC_EQUALSIZE) {
1337  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1338  if (child_wid->fill_y == 1) child_wid->smallest_y = highest;
1339  }
1340  }
1341  /* 3. Move PIP space to the child, compute smallest, fill, and resize values of the container. */
1342  if (this->head != NULL) this->head->padding_top += this->pip_pre;
1343  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1344  if (child_wid->next != NULL) {
1345  child_wid->padding_bottom += this->pip_inter;
1346  } else {
1347  child_wid->padding_bottom += this->pip_post;
1348  }
1349 
1350  this->smallest_y += child_wid->smallest_y + child_wid->padding_top + child_wid->padding_bottom;
1351  if (child_wid->fill_y > 0) {
1352  if (this->fill_y == 0 || this->fill_y > child_wid->fill_y) this->fill_y = child_wid->fill_y;
1353  }
1354  this->fill_x = LeastCommonMultiple(this->fill_x, child_wid->fill_x);
1355 
1356  if (child_wid->resize_y > 0) {
1357  if (this->resize_y == 0 || this->resize_y > child_wid->resize_y) this->resize_y = child_wid->resize_y;
1358  }
1359  this->resize_x = LeastCommonMultiple(this->resize_x, child_wid->resize_x);
1360  }
1361  /* We need to zero the PIP settings so we can re-initialize the tree. */
1362  this->pip_pre = this->pip_inter = this->pip_post = 0;
1363 }
1364 
1365 void NWidgetVertical::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
1366 {
1367  assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1368 
1369  /* Compute additional height given to us. */
1370  uint additional_length = given_height;
1371  if (sizing == ST_SMALLEST && (this->flags & NC_EQUALSIZE)) {
1372  /* For EQUALSIZE containers this does not sum to smallest_y during initialisation */
1373  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1374  additional_length -= child_wid->smallest_y + child_wid->padding_top + child_wid->padding_bottom;
1375  }
1376  } else {
1377  additional_length -= this->smallest_y;
1378  }
1379 
1380  this->StoreSizePosition(sizing, x, y, given_width, given_height);
1381 
1382  /* Like the horizontal container, the vertical container also distributes additional height evenly, starting with the children with the biggest resize steps.
1383  * It also stores computed widths and heights into current_x and current_y values of the child.
1384  */
1385 
1386  /* First loop: Find biggest stepsize, find number of children that want a piece of the pie, handle horizontal size for all children, handle vertical size for non-resizing child. */
1387  int num_changing_childs = 0; // Number of children that can change size.
1388  uint biggest_stepsize = 0;
1389  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1390  uint vert_step = child_wid->GetVerticalStepSize(sizing);
1391  if (vert_step > 0) {
1392  num_changing_childs++;
1393  biggest_stepsize = max(biggest_stepsize, vert_step);
1394  } else {
1395  child_wid->current_y = child_wid->smallest_y;
1396  }
1397 
1398  uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1399  child_wid->current_x = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding_left - child_wid->padding_right, hor_step);
1400  }
1401 
1402  /* Second loop: Allocate the additional vertical space over the resizing children, starting with the biggest resize steps. */
1403  while (biggest_stepsize > 0) {
1404  uint next_biggest_stepsize = 0;
1405  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1406  uint vert_step = child_wid->GetVerticalStepSize(sizing);
1407  if (vert_step > biggest_stepsize) continue; // Already done
1408  if (vert_step == biggest_stepsize) {
1409  uint increment = additional_length / num_changing_childs;
1410  num_changing_childs--;
1411  if (vert_step > 1) increment -= increment % vert_step;
1412  child_wid->current_y = child_wid->smallest_y + increment;
1413  additional_length -= increment;
1414  continue;
1415  }
1416  next_biggest_stepsize = max(next_biggest_stepsize, vert_step);
1417  }
1418  biggest_stepsize = next_biggest_stepsize;
1419  }
1420  assert(num_changing_childs == 0);
1421 
1422  /* Third loop: Compute position and call the child. */
1423  uint position = 0; // Place to put next child relative to origin of the container.
1424  for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
1425  uint child_x = x + (rtl ? child_wid->padding_right : child_wid->padding_left);
1426  uint child_height = child_wid->current_y;
1427 
1428  child_wid->AssignSizePosition(sizing, child_x, y + position + child_wid->padding_top, child_wid->current_x, child_height, rtl);
1429  position += child_height + child_wid->padding_top + child_wid->padding_bottom;
1430  }
1431 }
1432 
1439 {
1440  this->SetMinimalSize(length, height);
1441  this->SetResize(0, 0);
1442 }
1443 
1444 void NWidgetSpacer::SetupSmallestSize(Window *w, bool init_array)
1445 {
1446  this->smallest_x = this->min_x;
1447  this->smallest_y = this->min_y;
1448 }
1449 
1450 void NWidgetSpacer::FillNestedArray(NWidgetBase **array, uint length)
1451 {
1452 }
1453 
1455 {
1456  /* Spacer widget is never visible. */
1457 }
1458 
1459 void NWidgetSpacer::SetDirty(const Window *w) const
1460 {
1461  /* Spacer widget never need repainting. */
1462 }
1463 
1465 {
1466  return NULL;
1467 }
1468 
1469 NWidgetMatrix::NWidgetMatrix() : NWidgetPIPContainer(NWID_MATRIX, NC_EQUALSIZE), index(-1), clicked(-1), count(-1)
1470 {
1471 }
1472 
1473 void NWidgetMatrix::SetIndex(int index)
1474 {
1475  this->index = index;
1476 }
1477 
1478 void NWidgetMatrix::SetColour(Colours colour)
1479 {
1480  this->colour = colour;
1481 }
1482 
1487 void NWidgetMatrix::SetClicked(int clicked)
1488 {
1489  this->clicked = clicked;
1490  if (this->clicked >= 0 && this->sb != NULL && this->widgets_x != 0) {
1491  int vpos = (this->clicked / this->widgets_x) * this->widget_h; // Vertical position of the top.
1492  /* Need to scroll down -> Scroll to the bottom.
1493  * However, last entry has no 'this->pip_inter' underneath, and we must stay below this->sb->GetCount() */
1494  if (this->sb->GetPosition() < vpos) vpos += this->widget_h - this->pip_inter - 1;
1495  this->sb->ScrollTowards(vpos);
1496  }
1497 }
1498 
1505 {
1506  this->count = count;
1507 
1508  if (this->sb == NULL || this->widgets_x == 0) return;
1509 
1510  /* We need to get the number of pixels the matrix is high/wide.
1511  * So, determine the number of rows/columns based on the number of
1512  * columns/rows (one is constant/unscrollable).
1513  * Then multiply that by the height of a widget, and add the pre
1514  * and post spacing "offsets". */
1515  count = CeilDiv(count, this->sb->IsVertical() ? this->widgets_x : this->widgets_y);
1516  count *= (this->sb->IsVertical() ? this->head->smallest_y : this->head->smallest_x) + this->pip_inter;
1517  if (count > 0) count -= this->pip_inter; // We counted an inter too much in the multiplication above
1518  count += this->pip_pre + this->pip_post;
1519  this->sb->SetCount(count);
1520  this->sb->SetCapacity(this->sb->IsVertical() ? this->current_y : this->current_x);
1521  this->sb->SetStepSize(this->sb->IsVertical() ? this->widget_h : this->widget_w);
1522 }
1523 
1529 {
1530  this->sb = sb;
1531 }
1532 
1533 void NWidgetMatrix::SetupSmallestSize(Window *w, bool init_array)
1534 {
1535  assert(this->head != NULL);
1536  assert(this->head->next == NULL);
1537 
1538  if (this->index >= 0 && init_array) { // Fill w->nested_array[]
1539  assert(w->nested_array_size > (uint)this->index);
1540  w->nested_array[this->index] = this;
1541  }
1542 
1543  /* Reset the widget number. */
1544  NWidgetCore *nw = dynamic_cast<NWidgetCore *>(this->head);
1545  assert(nw != NULL);
1546  SB(nw->index, 16, 16, 0);
1547  this->head->SetupSmallestSize(w, init_array);
1548 
1549  Dimension padding = { (uint)this->pip_pre + this->pip_post, (uint)this->pip_pre + this->pip_post};
1550  Dimension size = {this->head->smallest_x + padding.width, this->head->smallest_y + padding.height};
1551  Dimension fill = {0, 0};
1552  Dimension resize = {this->pip_inter + this->head->smallest_x, this->pip_inter + this->head->smallest_y};
1553 
1554  if (this->index >= 0) w->UpdateWidgetSize(this->index, &size, padding, &fill, &resize);
1555 
1556  this->smallest_x = size.width;
1557  this->smallest_y = size.height;
1558  this->fill_x = fill.width;
1559  this->fill_y = fill.height;
1560  this->resize_x = resize.width;
1561  this->resize_y = resize.height;
1562 }
1563 
1564 void NWidgetMatrix::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
1565 {
1566  assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1567 
1568  this->pos_x = x;
1569  this->pos_y = y;
1570  this->current_x = given_width;
1571  this->current_y = given_height;
1572 
1573  /* Determine the size of the widgets, and the number of visible widgets on each of the axis. */
1574  this->widget_w = this->head->smallest_x + this->pip_inter;
1575  this->widget_h = this->head->smallest_y + this->pip_inter;
1576 
1577  /* Account for the pip_inter is between widgets, so we need to account for that when
1578  * the division assumes pip_inter is used for all widgets. */
1579  this->widgets_x = CeilDiv(this->current_x - this->pip_pre - this->pip_post + this->pip_inter, this->widget_w);
1580  this->widgets_y = CeilDiv(this->current_y - this->pip_pre - this->pip_post + this->pip_inter, this->widget_h);
1581 
1582  /* When resizing, update the scrollbar's count. E.g. with a vertical
1583  * scrollbar becoming wider or narrower means the amount of rows in
1584  * the scrollbar becomes respectively smaller or higher. */
1585  this->SetCount(this->count);
1586 }
1587 
1588 void NWidgetMatrix::FillNestedArray(NWidgetBase **array, uint length)
1589 {
1590  if (this->index >= 0 && (uint)(this->index) < length) array[this->index] = this;
1591  NWidgetContainer::FillNestedArray(array, length);
1592 }
1593 
1595 {
1596  /* Falls outside of the matrix widget. */
1597  if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
1598 
1599  int start_x, start_y, base_offs_x, base_offs_y;
1600  this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
1601 
1602  bool rtl = _current_text_dir == TD_RTL;
1603 
1604  int widget_col = (rtl ?
1605  -x + (int)this->pip_post + (int)this->pos_x + base_offs_x + (int)this->widget_w - 1 - (int)this->pip_inter :
1606  x - (int)this->pip_pre - (int)this->pos_x - base_offs_x
1607  ) / this->widget_w;
1608 
1609  int widget_row = (y - base_offs_y - (int)this->pip_pre - (int)this->pos_y) / this->widget_h;
1610 
1611  int sub_wid = (widget_row + start_y) * this->widgets_x + start_x + widget_col;
1612  if (sub_wid >= this->count) return NULL;
1613 
1614  NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->head);
1615  assert(child != NULL);
1617  this->pos_x + (rtl ? this->pip_post - widget_col * this->widget_w : this->pip_pre + widget_col * this->widget_w) + base_offs_x,
1618  this->pos_y + this->pip_pre + widget_row * this->widget_h + base_offs_y,
1619  child->smallest_x, child->smallest_y, rtl);
1620 
1621  SB(child->index, 16, 16, sub_wid);
1622 
1623  return child->GetWidgetFromPos(x, y);
1624 }
1625 
1626 /* virtual */ void NWidgetMatrix::Draw(const Window *w)
1627 {
1628  /* Fill the background. */
1629  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]);
1630 
1631  /* Set up a clipping area for the previews. */
1632  bool rtl = _current_text_dir == TD_RTL;
1633  DrawPixelInfo tmp_dpi;
1634  if (!FillDrawPixelInfo(&tmp_dpi, this->pos_x + (rtl ? this->pip_post : this->pip_pre), this->pos_y + this->pip_pre, this->current_x - this->pip_pre - this->pip_post, this->current_y - this->pip_pre - this->pip_post)) return;
1635  DrawPixelInfo *old_dpi = _cur_dpi;
1636  _cur_dpi = &tmp_dpi;
1637 
1638  /* Get the appropriate offsets so we can draw the right widgets. */
1639  NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->head);
1640  assert(child != NULL);
1641  int start_x, start_y, base_offs_x, base_offs_y;
1642  this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
1643 
1644  int offs_y = base_offs_y;
1645  for (int y = start_y; y < start_y + this->widgets_y + 1; y++, offs_y += this->widget_h) {
1646  /* Are we within bounds? */
1647  if (offs_y + child->smallest_y <= 0) continue;
1648  if (offs_y >= (int)this->current_y) break;
1649 
1650  /* We've passed our amount of widgets. */
1651  if (y * this->widgets_x >= this->count) break;
1652 
1653  int offs_x = base_offs_x;
1654  for (int x = start_x; x < start_x + this->widgets_x + 1; x++, offs_x += rtl ? -this->widget_w : this->widget_w) {
1655  /* Are we within bounds? */
1656  if (offs_x + child->smallest_x <= 0) continue;
1657  if (offs_x >= (int)this->current_x) continue;
1658 
1659  /* Do we have this many widgets? */
1660  int sub_wid = y * this->widgets_x + x;
1661  if (sub_wid >= this->count) break;
1662 
1663  child->AssignSizePosition(ST_RESIZE, offs_x, offs_y, child->smallest_x, child->smallest_y, rtl);
1664  child->SetLowered(this->clicked == sub_wid);
1665  SB(child->index, 16, 16, sub_wid);
1666  child->Draw(w);
1667  }
1668  }
1669 
1670  /* Restore the clipping area. */
1671  _cur_dpi = old_dpi;
1672 }
1673 
1681 void NWidgetMatrix::GetScrollOffsets(int &start_x, int &start_y, int &base_offs_x, int &base_offs_y)
1682 {
1683  base_offs_x = _current_text_dir == TD_RTL ? this->widget_w * (this->widgets_x - 1) : 0;
1684  base_offs_y = 0;
1685  start_x = 0;
1686  start_y = 0;
1687  if (this->sb != NULL) {
1688  if (this->sb->IsVertical()) {
1689  start_y = this->sb->GetPosition() / this->widget_h;
1690  base_offs_y += -this->sb->GetPosition() + start_y * this->widget_h;
1691  } else {
1692  start_x = this->sb->GetPosition() / this->widget_w;
1693  int sub_x = this->sb->GetPosition() - start_x * this->widget_w;
1694  if (_current_text_dir == TD_RTL) {
1695  base_offs_x += sub_x;
1696  } else {
1697  base_offs_x -= sub_x;
1698  }
1699  }
1700  }
1701 }
1702 
1712 NWidgetBackground::NWidgetBackground(WidgetType tp, Colours colour, int index, NWidgetPIPContainer *child) : NWidgetCore(tp, colour, 1, 1, 0x0, STR_NULL)
1713 {
1714  assert(tp == WWT_PANEL || tp == WWT_INSET || tp == WWT_FRAME);
1715  if (index >= 0) this->SetIndex(index);
1716  this->child = child;
1717 }
1718 
1719 NWidgetBackground::~NWidgetBackground()
1720 {
1721  if (this->child != NULL) delete this->child;
1722 }
1723 
1732 {
1733  if (this->child == NULL) {
1734  this->child = new NWidgetVertical();
1735  }
1736  this->child->Add(nwid);
1737 }
1738 
1749 void NWidgetBackground::SetPIP(uint8 pip_pre, uint8 pip_inter, uint8 pip_post)
1750 {
1751  if (this->child == NULL) {
1752  this->child = new NWidgetVertical();
1753  }
1754  this->child->SetPIP(pip_pre, pip_inter, pip_post);
1755 }
1756 
1758 {
1759  if (init_array && this->index >= 0) {
1760  assert(w->nested_array_size > (uint)this->index);
1761  w->nested_array[this->index] = this;
1762  }
1763  if (this->child != NULL) {
1764  this->child->SetupSmallestSize(w, init_array);
1765 
1766  this->smallest_x = this->child->smallest_x;
1767  this->smallest_y = this->child->smallest_y;
1768  this->fill_x = this->child->fill_x;
1769  this->fill_y = this->child->fill_y;
1770  this->resize_x = this->child->resize_x;
1771  this->resize_y = this->child->resize_y;
1772 
1773  /* Account for the size of the frame's text if that exists */
1774  if (w != NULL && this->type == WWT_FRAME) {
1777  this->child->padding_top = max((int)WD_FRAMETEXT_TOP, this->widget_data != STR_NULL ? FONT_HEIGHT_NORMAL + WD_FRAMETEXT_TOP / 2 : 0);
1779 
1780  this->smallest_x += this->child->padding_left + this->child->padding_right;
1781  this->smallest_y += this->child->padding_top + this->child->padding_bottom;
1782 
1783  if (this->index >= 0) w->SetStringParameters(this->index);
1785  }
1786  } else {
1787  Dimension d = {this->min_x, this->min_y};
1788  Dimension fill = {this->fill_x, this->fill_y};
1789  Dimension resize = {this->resize_x, this->resize_y};
1790  if (w != NULL) { // A non-NULL window pointer acts as switch to turn dynamic widget size on.
1791  if (this->type == WWT_FRAME || this->type == WWT_INSET) {
1792  if (this->index >= 0) w->SetStringParameters(this->index);
1793  Dimension background = GetStringBoundingBox(this->widget_data);
1794  background.width += (this->type == WWT_FRAME) ? (WD_FRAMETEXT_LEFT + WD_FRAMERECT_RIGHT) : (WD_INSET_LEFT + WD_INSET_RIGHT);
1795  d = maxdim(d, background);
1796  }
1797  if (this->index >= 0) {
1798  static const Dimension padding = {0, 0};
1799  w->UpdateWidgetSize(this->index, &d, padding, &fill, &resize);
1800  }
1801  }
1802  this->smallest_x = d.width;
1803  this->smallest_y = d.height;
1804  this->fill_x = fill.width;
1805  this->fill_y = fill.height;
1806  this->resize_x = resize.width;
1807  this->resize_y = resize.height;
1808  }
1809 }
1810 
1811 void NWidgetBackground::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
1812 {
1813  this->StoreSizePosition(sizing, x, y, given_width, given_height);
1814 
1815  if (this->child != NULL) {
1816  uint x_offset = (rtl ? this->child->padding_right : this->child->padding_left);
1817  uint width = given_width - this->child->padding_right - this->child->padding_left;
1818  uint height = given_height - this->child->padding_top - this->child->padding_bottom;
1819  this->child->AssignSizePosition(sizing, x + x_offset, y + this->child->padding_top, width, height, rtl);
1820  }
1821 }
1822 
1823 void NWidgetBackground::FillNestedArray(NWidgetBase **array, uint length)
1824 {
1825  if (this->index >= 0 && (uint)(this->index) < length) array[this->index] = this;
1826  if (this->child != NULL) this->child->FillNestedArray(array, length);
1827 }
1828 
1830 {
1831  if (this->current_x == 0 || this->current_y == 0) return;
1832 
1833  Rect r;
1834  r.left = this->pos_x;
1835  r.right = this->pos_x + this->current_x - 1;
1836  r.top = this->pos_y;
1837  r.bottom = this->pos_y + this->current_y - 1;
1838 
1839  const DrawPixelInfo *dpi = _cur_dpi;
1840  if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
1841 
1842  switch (this->type) {
1843  case WWT_PANEL:
1844  assert(this->widget_data == 0);
1845  DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, this->IsLowered() ? FR_LOWERED : FR_NONE);
1846  break;
1847 
1848  case WWT_FRAME:
1849  if (this->index >= 0) w->SetStringParameters(this->index);
1850  DrawFrame(r, this->colour, this->widget_data);
1851  break;
1852 
1853  case WWT_INSET:
1854  if (this->index >= 0) w->SetStringParameters(this->index);
1855  DrawInset(r, this->colour, this->widget_data);
1856  break;
1857 
1858  default:
1859  NOT_REACHED();
1860  }
1861 
1862  if (this->index >= 0) w->DrawWidget(r, this->index);
1863  if (this->child != NULL) this->child->Draw(w);
1864 
1865  if (this->IsDisabled()) {
1866  GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, _colour_gradient[this->colour & 0xF][2], FILLRECT_CHECKER);
1867  }
1868 }
1869 
1871 {
1872  NWidgetCore *nwid = NULL;
1873  if (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) {
1874  if (this->child != NULL) nwid = this->child->GetWidgetFromPos(x, y);
1875  if (nwid == NULL) nwid = this;
1876  }
1877  return nwid;
1878 }
1879 
1881 {
1882  NWidgetBase *nwid = NULL;
1883  if (this->child != NULL) nwid = this->child->GetWidgetOfType(tp);
1884  if (nwid == NULL && this->type == tp) nwid = this;
1885  return nwid;
1886 }
1887 
1888 NWidgetViewport::NWidgetViewport(int index) : NWidgetCore(NWID_VIEWPORT, INVALID_COLOUR, 1, 1, 0x0, STR_NULL)
1889 {
1890  this->SetIndex(index);
1891 }
1892 
1894 {
1895  if (init_array && this->index >= 0) {
1896  assert(w->nested_array_size > (uint)this->index);
1897  w->nested_array[this->index] = this;
1898  }
1899  this->smallest_x = this->min_x;
1900  this->smallest_y = this->min_y;
1901 }
1902 
1904 {
1905  if (this->disp_flags & ND_NO_TRANSPARENCY) {
1907  _transparency_opt &= (1 << TO_SIGNS) | (1 << TO_LOADING); // Disable all transparency, except textual stuff
1908  w->DrawViewport();
1909  _transparency_opt = to_backup;
1910  } else {
1911  w->DrawViewport();
1912  }
1913 
1914  /* Optionally shade the viewport. */
1915  if (this->disp_flags & (ND_SHADE_GREY | ND_SHADE_DIMMED)) {
1916  GfxFillRect(this->pos_x, this->pos_y, this->pos_x + this->current_x - 1, this->pos_y + this->current_y - 1,
1918  }
1919 }
1920 
1927 void NWidgetViewport::InitializeViewport(Window *w, uint32 follow_flags, ZoomLevel zoom)
1928 {
1929  InitializeWindowViewport(w, this->pos_x, this->pos_y, this->current_x, this->current_y, follow_flags, zoom);
1930 }
1931 
1937 {
1938  ViewPort *vp = w->viewport;
1939  if (vp != NULL) {
1940  vp->left = w->left + this->pos_x;
1941  vp->top = w->top + this->pos_y;
1942  vp->width = this->current_x;
1943  vp->height = this->current_y;
1944 
1945  vp->virtual_width = ScaleByZoom(vp->width, vp->zoom);
1946  vp->virtual_height = ScaleByZoom(vp->height, vp->zoom);
1947  }
1948 }
1949 
1959 int Scrollbar::GetScrolledRowFromWidget(int clickpos, const Window * const w, int widget, int padding, int line_height) const
1960 {
1961  uint pos = w->GetRowFromWidget(clickpos, widget, padding, line_height);
1962  if (pos != INT_MAX) pos += this->GetPosition();
1963  return (pos >= this->GetCount()) ? INT_MAX : pos;
1964 }
1965 
1973 void Scrollbar::SetCapacityFromWidget(Window *w, int widget, int padding)
1974 {
1975  NWidgetBase *nwid = w->GetWidget<NWidgetBase>(widget);
1976  if (this->IsVertical()) {
1977  this->SetCapacity(((int)nwid->current_y - padding) / (int)nwid->resize_y);
1978  } else {
1979  this->SetCapacity(((int)nwid->current_x - padding) / (int)nwid->resize_x);
1980  }
1981 }
1982 
1989 NWidgetScrollbar::NWidgetScrollbar(WidgetType tp, Colours colour, int index) : NWidgetCore(tp, colour, 1, 1, 0x0, STR_NULL), Scrollbar(tp != NWID_HSCROLLBAR)
1990 {
1991  assert(tp == NWID_HSCROLLBAR || tp == NWID_VSCROLLBAR);
1992  this->SetIndex(index);
1993 }
1994 
1996 {
1997  if (init_array && this->index >= 0) {
1998  assert(w->nested_array_size > (uint)this->index);
1999  w->nested_array[this->index] = this;
2000  }
2001  this->min_x = 0;
2002  this->min_y = 0;
2003 
2004  switch (this->type) {
2005  case NWID_HSCROLLBAR:
2006  this->SetMinimalSize(NWidgetScrollbar::GetHorizontalDimension().width * 3, NWidgetScrollbar::GetHorizontalDimension().height);
2007  this->SetResize(1, 0);
2008  this->SetFill(1, 0);
2009  this->SetDataTip(0x0, STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST);
2010  break;
2011 
2012  case NWID_VSCROLLBAR:
2013  this->SetMinimalSize(NWidgetScrollbar::GetVerticalDimension().width, NWidgetScrollbar::GetVerticalDimension().height * 3);
2014  this->SetResize(0, 1);
2015  this->SetFill(0, 1);
2016  this->SetDataTip(0x0, STR_TOOLTIP_VSCROLL_BAR_SCROLLS_LIST);
2017  break;
2018 
2019  default: NOT_REACHED();
2020  }
2021 
2022  this->smallest_x = this->min_x;
2023  this->smallest_y = this->min_y;
2024 }
2025 
2027 {
2028  if (this->current_x == 0 || this->current_y == 0) return;
2029 
2030  Rect r;
2031  r.left = this->pos_x;
2032  r.right = this->pos_x + this->current_x - 1;
2033  r.top = this->pos_y;
2034  r.bottom = this->pos_y + this->current_y - 1;
2035 
2036  const DrawPixelInfo *dpi = _cur_dpi;
2037  if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2038 
2039  bool up_lowered = HasBit(this->disp_flags, NDB_SCROLLBAR_UP);
2040  bool down_lowered = HasBit(this->disp_flags, NDB_SCROLLBAR_DOWN);
2041  bool middle_lowered = !(this->disp_flags & ND_SCROLLBAR_BTN) && w->scrolling_scrollbar == this->index;
2042 
2043  if (this->type == NWID_HSCROLLBAR) {
2044  DrawHorizontalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2045  } else {
2046  DrawVerticalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2047  }
2048 
2049  if (this->IsDisabled()) {
2050  GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, _colour_gradient[this->colour & 0xF][2], FILLRECT_CHECKER);
2051  }
2052 }
2053 
2054 /* static */ void NWidgetScrollbar::InvalidateDimensionCache()
2055 {
2056  vertical_dimension.width = vertical_dimension.height = 0;
2057  horizontal_dimension.width = horizontal_dimension.height = 0;
2058 }
2059 
2060 /* static */ Dimension NWidgetScrollbar::GetVerticalDimension()
2061 {
2063  if (vertical_dimension.width == 0) {
2064  vertical_dimension = maxdim(GetSpriteSize(SPR_ARROW_UP), GetSpriteSize(SPR_ARROW_DOWN));
2065  vertical_dimension.width += extra.width;
2066  vertical_dimension.height += extra.height;
2067  }
2068  return vertical_dimension;
2069 }
2070 
2071 /* static */ Dimension NWidgetScrollbar::GetHorizontalDimension()
2072 {
2074  if (horizontal_dimension.width == 0) {
2075  horizontal_dimension = maxdim(GetSpriteSize(SPR_ARROW_LEFT), GetSpriteSize(SPR_ARROW_RIGHT));
2076  horizontal_dimension.width += extra.width;
2077  horizontal_dimension.height += extra.height;
2078  }
2079  return horizontal_dimension;
2080 }
2081 
2084 
2087 {
2088  shadebox_dimension.width = shadebox_dimension.height = 0;
2089  debugbox_dimension.width = debugbox_dimension.height = 0;
2090  defsizebox_dimension.width = defsizebox_dimension.height = 0;
2091  stickybox_dimension.width = stickybox_dimension.height = 0;
2092  resizebox_dimension.width = resizebox_dimension.height = 0;
2093  closebox_dimension.width = closebox_dimension.height = 0;
2094  dropdown_dimension.width = dropdown_dimension.height = 0;
2095 }
2096 
2104 
2113 NWidgetLeaf::NWidgetLeaf(WidgetType tp, Colours colour, int index, uint32 data, StringID tip) : NWidgetCore(tp, colour, 1, 1, data, tip)
2114 {
2115  assert(index >= 0 || tp == WWT_LABEL || tp == WWT_TEXT || tp == WWT_CAPTION || tp == WWT_RESIZEBOX || tp == WWT_SHADEBOX || tp == WWT_DEFSIZEBOX || tp == WWT_DEBUGBOX || tp == WWT_STICKYBOX || tp == WWT_CLOSEBOX);
2116  if (index >= 0) this->SetIndex(index);
2117  this->min_x = 0;
2118  this->min_y = 0;
2119  this->SetResize(0, 0);
2120 
2121  switch (tp) {
2122  case WWT_EMPTY:
2123  break;
2124 
2125  case WWT_PUSHBTN:
2126  case WWT_IMGBTN:
2127  case WWT_PUSHIMGBTN:
2128  case WWT_IMGBTN_2:
2129  case WWT_TEXTBTN:
2130  case WWT_PUSHTXTBTN:
2131  case WWT_TEXTBTN_2:
2132  case WWT_LABEL:
2133  case WWT_TEXT:
2134  case WWT_MATRIX:
2135  case NWID_BUTTON_DROPDOWN:
2136  case NWID_PUSHBUTTON_DROPDOWN:
2137  case WWT_ARROWBTN:
2138  case WWT_PUSHARROWBTN:
2139  this->SetFill(0, 0);
2140  break;
2141 
2142  case WWT_EDITBOX:
2143  this->SetFill(0, 0);
2144  break;
2145 
2146  case WWT_CAPTION:
2147  this->SetFill(1, 0);
2148  this->SetResize(1, 0);
2149  this->min_y = WD_CAPTION_HEIGHT;
2150  this->SetDataTip(data, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
2151  break;
2152 
2153  case WWT_STICKYBOX:
2154  this->SetFill(0, 0);
2156  this->SetDataTip(STR_NULL, STR_TOOLTIP_STICKY);
2157  break;
2158 
2159  case WWT_SHADEBOX:
2160  this->SetFill(0, 0);
2162  this->SetDataTip(STR_NULL, STR_TOOLTIP_SHADE);
2163  break;
2164 
2165  case WWT_DEBUGBOX:
2166  this->SetFill(0, 0);
2168  this->SetDataTip(STR_NULL, STR_TOOLTIP_DEBUG);
2169  break;
2170 
2171  case WWT_DEFSIZEBOX:
2172  this->SetFill(0, 0);
2174  this->SetDataTip(STR_NULL, STR_TOOLTIP_DEFSIZE);
2175  break;
2176 
2177  case WWT_RESIZEBOX:
2178  this->SetFill(0, 0);
2180  this->SetDataTip(STR_NULL, STR_TOOLTIP_RESIZE);
2181  break;
2182 
2183  case WWT_CLOSEBOX:
2184  this->SetFill(0, 0);
2186  this->SetDataTip(STR_NULL, STR_TOOLTIP_CLOSE_WINDOW);
2187  break;
2188 
2189  case WWT_DROPDOWN:
2190  this->SetFill(0, 0);
2191  this->min_y = WD_DROPDOWN_HEIGHT;
2192  break;
2193 
2194  default:
2195  NOT_REACHED();
2196  }
2197 }
2198 
2199 void NWidgetLeaf::SetupSmallestSize(Window *w, bool init_array)
2200 {
2201  if (this->index >= 0 && init_array) { // Fill w->nested_array[]
2202  assert(w->nested_array_size > (uint)this->index);
2203  w->nested_array[this->index] = this;
2204  }
2205 
2206  Dimension size = {this->min_x, this->min_y};
2207  Dimension fill = {this->fill_x, this->fill_y};
2208  Dimension resize = {this->resize_x, this->resize_y};
2209  /* Get padding, and update size with the real content size if appropriate. */
2210  const Dimension *padding = NULL;
2211  switch (this->type) {
2212  case WWT_EMPTY: {
2213  static const Dimension extra = {0, 0};
2214  padding = &extra;
2215  break;
2216  }
2217  case WWT_MATRIX: {
2219  padding = &extra;
2220  break;
2221  }
2222  case WWT_SHADEBOX: {
2224  padding = &extra;
2225  if (NWidgetLeaf::shadebox_dimension.width == 0) {
2226  NWidgetLeaf::shadebox_dimension = maxdim(GetSpriteSize(SPR_WINDOW_SHADE), GetSpriteSize(SPR_WINDOW_UNSHADE));
2227  NWidgetLeaf::shadebox_dimension.width += extra.width;
2228  NWidgetLeaf::shadebox_dimension.height += extra.height;
2229  }
2230  size = maxdim(size, NWidgetLeaf::shadebox_dimension);
2231  break;
2232  }
2233  case WWT_DEBUGBOX:
2236  padding = &extra;
2237  if (NWidgetLeaf::debugbox_dimension.width == 0) {
2238  NWidgetLeaf::debugbox_dimension = GetSpriteSize(SPR_WINDOW_DEBUG);
2239  NWidgetLeaf::debugbox_dimension.width += extra.width;
2240  NWidgetLeaf::debugbox_dimension.height += extra.height;
2241  }
2242  size = maxdim(size, NWidgetLeaf::debugbox_dimension);
2243  } else {
2244  /* If the setting is disabled we don't want to see it! */
2245  size.width = 0;
2246  fill.width = 0;
2247  resize.width = 0;
2248  }
2249  break;
2250 
2251  case WWT_STICKYBOX: {
2253  padding = &extra;
2254  if (NWidgetLeaf::stickybox_dimension.width == 0) {
2255  NWidgetLeaf::stickybox_dimension = maxdim(GetSpriteSize(SPR_PIN_UP), GetSpriteSize(SPR_PIN_DOWN));
2256  NWidgetLeaf::stickybox_dimension.width += extra.width;
2257  NWidgetLeaf::stickybox_dimension.height += extra.height;
2258  }
2259  size = maxdim(size, NWidgetLeaf::stickybox_dimension);
2260  break;
2261  }
2262 
2263  case WWT_DEFSIZEBOX: {
2265  padding = &extra;
2266  if (NWidgetLeaf::defsizebox_dimension.width == 0) {
2267  NWidgetLeaf::defsizebox_dimension = GetSpriteSize(SPR_WINDOW_DEFSIZE);
2268  NWidgetLeaf::defsizebox_dimension.width += extra.width;
2269  NWidgetLeaf::defsizebox_dimension.height += extra.height;
2270  }
2271  size = maxdim(size, NWidgetLeaf::defsizebox_dimension);
2272  break;
2273  }
2274 
2275  case WWT_RESIZEBOX: {
2277  padding = &extra;
2278  if (NWidgetLeaf::resizebox_dimension.width == 0) {
2279  NWidgetLeaf::resizebox_dimension = maxdim(GetSpriteSize(SPR_WINDOW_RESIZE_LEFT), GetSpriteSize(SPR_WINDOW_RESIZE_RIGHT));
2280  NWidgetLeaf::resizebox_dimension.width += extra.width;
2281  NWidgetLeaf::resizebox_dimension.height += extra.height;
2282  }
2283  size = maxdim(size, NWidgetLeaf::resizebox_dimension);
2284  break;
2285  }
2286  case WWT_EDITBOX: {
2287  Dimension sprite_size = GetSpriteSize(_current_text_dir == TD_RTL ? SPR_IMG_DELETE_RIGHT : SPR_IMG_DELETE_LEFT);
2288  size.width = max(size.width, 30 + sprite_size.width);
2289  size.height = max(sprite_size.height, GetStringBoundingBox("_").height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM);
2290  }
2291  FALLTHROUGH;
2292  case WWT_PUSHBTN: {
2294  padding = &extra;
2295  break;
2296  }
2297  case WWT_IMGBTN:
2298  case WWT_IMGBTN_2:
2299  case WWT_PUSHIMGBTN: {
2301  padding = &extra;
2302  Dimension d2 = GetSpriteSize(this->widget_data);
2303  if (this->type == WWT_IMGBTN_2) d2 = maxdim(d2, GetSpriteSize(this->widget_data + 1));
2304  d2.width += extra.width;
2305  d2.height += extra.height;
2306  size = maxdim(size, d2);
2307  break;
2308  }
2309  case WWT_ARROWBTN:
2310  case WWT_PUSHARROWBTN: {
2312  padding = &extra;
2313  Dimension d2 = maxdim(GetSpriteSize(SPR_ARROW_LEFT), GetSpriteSize(SPR_ARROW_RIGHT));
2314  d2.width += extra.width;
2315  d2.height += extra.height;
2316  size = maxdim(size, d2);
2317  break;
2318  }
2319 
2320  case WWT_CLOSEBOX: {
2322  padding = &extra;
2323  if (NWidgetLeaf::closebox_dimension.width == 0) {
2324  NWidgetLeaf::closebox_dimension = GetSpriteSize(SPR_CLOSEBOX);
2325  NWidgetLeaf::closebox_dimension.width += extra.width;
2326  NWidgetLeaf::closebox_dimension.height += extra.height;
2327  }
2328  size = maxdim(size, NWidgetLeaf::closebox_dimension);
2329  break;
2330  }
2331  case WWT_TEXTBTN:
2332  case WWT_PUSHTXTBTN:
2333  case WWT_TEXTBTN_2: {
2335  padding = &extra;
2336  if (this->index >= 0) w->SetStringParameters(this->index);
2338  d2.width += extra.width;
2339  d2.height += extra.height;
2340  size = maxdim(size, d2);
2341  break;
2342  }
2343  case WWT_LABEL:
2344  case WWT_TEXT: {
2345  static const Dimension extra = {0, 0};
2346  padding = &extra;
2347  if (this->index >= 0) w->SetStringParameters(this->index);
2348  size = maxdim(size, GetStringBoundingBox(this->widget_data));
2349  break;
2350  }
2351  case WWT_CAPTION: {
2353  padding = &extra;
2354  if (this->index >= 0) w->SetStringParameters(this->index);
2356  d2.width += extra.width;
2357  d2.height += extra.height;
2358  size = maxdim(size, d2);
2359  break;
2360  }
2361  case WWT_DROPDOWN:
2362  case NWID_BUTTON_DROPDOWN:
2363  case NWID_PUSHBUTTON_DROPDOWN: {
2365  padding = &extra;
2366  if (NWidgetLeaf::dropdown_dimension.width == 0) {
2367  NWidgetLeaf::dropdown_dimension = GetSpriteSize(SPR_ARROW_DOWN);
2368  NWidgetLeaf::dropdown_dimension.width += WD_DROPDOWNTEXT_LEFT + WD_DROPDOWNTEXT_RIGHT;
2369  NWidgetLeaf::dropdown_dimension.height += WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM;
2370  extra.width = WD_DROPDOWNTEXT_LEFT + WD_DROPDOWNTEXT_RIGHT + NWidgetLeaf::dropdown_dimension.width;
2371  }
2372  if (this->index >= 0) w->SetStringParameters(this->index);
2374  d2.width += extra.width;
2375  d2.height = max(d2.height, NWidgetLeaf::dropdown_dimension.height) + extra.height;
2376  size = maxdim(size, d2);
2377  break;
2378  }
2379  default:
2380  NOT_REACHED();
2381  }
2382 
2383  if (this->index >= 0) w->UpdateWidgetSize(this->index, &size, *padding, &fill, &resize);
2384 
2385  this->smallest_x = size.width;
2386  this->smallest_y = size.height;
2387  this->fill_x = fill.width;
2388  this->fill_y = fill.height;
2389  this->resize_x = resize.width;
2390  this->resize_y = resize.height;
2391 }
2392 
2394 {
2395  if (this->current_x == 0 || this->current_y == 0) return;
2396 
2397  /* Setup a clipping rectangle... */
2398  DrawPixelInfo new_dpi;
2399  if (!FillDrawPixelInfo(&new_dpi, this->pos_x, this->pos_y, this->current_x, this->current_y)) return;
2400  /* ...but keep coordinates relative to the window. */
2401  new_dpi.left += this->pos_x;
2402  new_dpi.top += this->pos_y;
2403 
2404  DrawPixelInfo *old_dpi = _cur_dpi;
2405  _cur_dpi = &new_dpi;
2406 
2407  Rect r;
2408  r.left = this->pos_x;
2409  r.right = this->pos_x + this->current_x - 1;
2410  r.top = this->pos_y;
2411  r.bottom = this->pos_y + this->current_y - 1;
2412 
2413  bool clicked = this->IsLowered();
2414  switch (this->type) {
2415  case WWT_EMPTY:
2416  break;
2417 
2418  case WWT_PUSHBTN:
2419  assert(this->widget_data == 0);
2420  DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FR_LOWERED : FR_NONE);
2421  break;
2422 
2423  case WWT_IMGBTN:
2424  case WWT_PUSHIMGBTN:
2425  case WWT_IMGBTN_2:
2426  DrawImageButtons(r, this->type, this->colour, clicked, this->widget_data);
2427  break;
2428 
2429  case WWT_TEXTBTN:
2430  case WWT_PUSHTXTBTN:
2431  case WWT_TEXTBTN_2:
2432  if (this->index >= 0) w->SetStringParameters(this->index);
2433  DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FR_LOWERED : FR_NONE);
2434  DrawLabel(r, this->type, clicked, this->widget_data);
2435  break;
2436 
2437  case WWT_ARROWBTN:
2438  case WWT_PUSHARROWBTN: {
2439  SpriteID sprite;
2440  switch (this->widget_data) {
2441  case AWV_DECREASE: sprite = _current_text_dir != TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
2442  case AWV_INCREASE: sprite = _current_text_dir == TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
2443  case AWV_LEFT: sprite = SPR_ARROW_LEFT; break;
2444  case AWV_RIGHT: sprite = SPR_ARROW_RIGHT; break;
2445  default: NOT_REACHED();
2446  }
2447  DrawImageButtons(r, WWT_PUSHIMGBTN, this->colour, clicked, sprite);
2448  break;
2449  }
2450 
2451  case WWT_LABEL:
2452  if (this->index >= 0) w->SetStringParameters(this->index);
2453  DrawLabel(r, this->type, clicked, this->widget_data);
2454  break;
2455 
2456  case WWT_TEXT:
2457  if (this->index >= 0) w->SetStringParameters(this->index);
2458  DrawText(r, (TextColour)this->colour, this->widget_data);
2459  break;
2460 
2461  case WWT_MATRIX:
2462  DrawMatrix(r, this->colour, clicked, this->widget_data, this->resize_x, this->resize_y);
2463  break;
2464 
2465  case WWT_EDITBOX: {
2466  const QueryString *query = w->GetQueryString(this->index);
2467  if (query != NULL) query->DrawEditBox(w, this->index);
2468  break;
2469  }
2470 
2471  case WWT_CAPTION:
2472  if (this->index >= 0) w->SetStringParameters(this->index);
2473  DrawCaption(r, this->colour, w->owner, this->widget_data);
2474  break;
2475 
2476  case WWT_SHADEBOX:
2477  assert(this->widget_data == 0);
2478  DrawShadeBox(r, this->colour, w->IsShaded());
2479  break;
2480 
2481  case WWT_DEBUGBOX:
2482  DrawDebugBox(r, this->colour, clicked);
2483  break;
2484 
2485  case WWT_STICKYBOX:
2486  assert(this->widget_data == 0);
2487  DrawStickyBox(r, this->colour, !!(w->flags & WF_STICKY));
2488  break;
2489 
2490  case WWT_DEFSIZEBOX:
2491  assert(this->widget_data == 0);
2492  DrawDefSizeBox(r, this->colour, clicked);
2493  break;
2494 
2495  case WWT_RESIZEBOX:
2496  assert(this->widget_data == 0);
2497  DrawResizeBox(r, this->colour, this->pos_x < (uint)(w->width / 2), !!(w->flags & WF_SIZING));
2498  break;
2499 
2500  case WWT_CLOSEBOX:
2501  DrawCloseBox(r, this->colour);
2502  break;
2503 
2504  case WWT_DROPDOWN:
2505  if (this->index >= 0) w->SetStringParameters(this->index);
2506  DrawDropdown(r, this->colour, clicked, this->widget_data);
2507  break;
2508 
2509  case NWID_BUTTON_DROPDOWN:
2510  case NWID_PUSHBUTTON_DROPDOWN:
2511  if (this->index >= 0) w->SetStringParameters(this->index);
2512  DrawButtonDropdown(r, this->colour, clicked, (this->disp_flags & ND_DROPDOWN_ACTIVE) != 0, this->widget_data);
2513  break;
2514 
2515  default:
2516  NOT_REACHED();
2517  }
2518  if (this->index >= 0) w->DrawWidget(r, this->index);
2519 
2520  if (this->IsDisabled()) {
2521  GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, _colour_gradient[this->colour & 0xF][2], FILLRECT_CHECKER);
2522  }
2523 
2524  _cur_dpi = old_dpi;
2525 }
2526 
2535 {
2536  if (_current_text_dir == TD_LTR) {
2537  int button_width = this->pos_x + this->current_x - NWidgetLeaf::dropdown_dimension.width;
2538  return pt.x < button_width;
2539  } else {
2540  int button_left = this->pos_x + NWidgetLeaf::dropdown_dimension.width;
2541  return pt.x >= button_left;
2542  }
2543 }
2544 
2545 /* == Conversion code from NWidgetPart array to NWidgetBase* tree == */
2546 
2562 static int MakeNWidget(const NWidgetPart *parts, int count, NWidgetBase **dest, bool *fill_dest, int *biggest_index)
2563 {
2564  int num_used = 0;
2565 
2566  *dest = NULL;
2567  *fill_dest = false;
2568 
2569  while (count > num_used) {
2570  switch (parts->type) {
2571  case NWID_SPACER:
2572  if (*dest != NULL) return num_used;
2573  *dest = new NWidgetSpacer(0, 0);
2574  break;
2575 
2576  case NWID_HORIZONTAL:
2577  if (*dest != NULL) return num_used;
2578  *dest = new NWidgetHorizontal(parts->u.cont_flags);
2579  *fill_dest = true;
2580  break;
2581 
2582  case NWID_HORIZONTAL_LTR:
2583  if (*dest != NULL) return num_used;
2584  *dest = new NWidgetHorizontalLTR(parts->u.cont_flags);
2585  *fill_dest = true;
2586  break;
2587 
2588  case WWT_PANEL:
2589  case WWT_INSET:
2590  case WWT_FRAME:
2591  if (*dest != NULL) return num_used;
2592  *dest = new NWidgetBackground(parts->type, parts->u.widget.colour, parts->u.widget.index);
2593  *biggest_index = max(*biggest_index, (int)parts->u.widget.index);
2594  *fill_dest = true;
2595  break;
2596 
2597  case NWID_VERTICAL:
2598  if (*dest != NULL) return num_used;
2599  *dest = new NWidgetVertical(parts->u.cont_flags);
2600  *fill_dest = true;
2601  break;
2602 
2603  case NWID_MATRIX: {
2604  if (*dest != NULL) return num_used;
2605  NWidgetMatrix *nwm = new NWidgetMatrix();
2606  *dest = nwm;
2607  *fill_dest = true;
2608  nwm->SetIndex(parts->u.widget.index);
2609  nwm->SetColour(parts->u.widget.colour);
2610  *biggest_index = max(*biggest_index, (int)parts->u.widget.index);
2611  break;
2612  }
2613 
2614  case WPT_FUNCTION: {
2615  if (*dest != NULL) return num_used;
2616  /* Ensure proper functioning even when the called code simply writes its largest index. */
2617  int biggest = -1;
2618  *dest = parts->u.func_ptr(&biggest);
2619  *biggest_index = max(*biggest_index, biggest);
2620  *fill_dest = false;
2621  break;
2622  }
2623 
2624  case WPT_RESIZE: {
2625  NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(*dest);
2626  if (nwrb != NULL) {
2627  assert(parts->u.xy.x >= 0 && parts->u.xy.y >= 0);
2628  nwrb->SetResize(parts->u.xy.x, parts->u.xy.y);
2629  }
2630  break;
2631  }
2632 
2633  case WPT_MINSIZE: {
2634  NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(*dest);
2635  if (nwrb != NULL) {
2636  assert(parts->u.xy.x >= 0 && parts->u.xy.y >= 0);
2637  nwrb->SetMinimalSize(parts->u.xy.x, parts->u.xy.y);
2638  }
2639  break;
2640  }
2641 
2642  case WPT_MINTEXTLINES: {
2643  NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(*dest);
2644  if (nwrb != NULL) {
2645  assert(parts->u.text_lines.size >= FS_BEGIN && parts->u.text_lines.size < FS_END);
2646  nwrb->SetMinimalTextLines(parts->u.text_lines.lines, parts->u.text_lines.spacing, parts->u.text_lines.size);
2647  }
2648  break;
2649  }
2650 
2651  case WPT_FILL: {
2652  NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(*dest);
2653  if (nwrb != NULL) nwrb->SetFill(parts->u.xy.x, parts->u.xy.y);
2654  break;
2655  }
2656 
2657  case WPT_DATATIP: {
2658  NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(*dest);
2659  if (nwc != NULL) {
2660  nwc->widget_data = parts->u.data_tip.data;
2661  nwc->tool_tip = parts->u.data_tip.tooltip;
2662  }
2663  break;
2664  }
2665 
2666  case WPT_PADDING:
2667  if (*dest != NULL) (*dest)->SetPadding(parts->u.padding.top, parts->u.padding.right, parts->u.padding.bottom, parts->u.padding.left);
2668  break;
2669 
2670  case WPT_PIPSPACE: {
2671  NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(*dest);
2672  if (nwc != NULL) nwc->SetPIP(parts->u.pip.pre, parts->u.pip.inter, parts->u.pip.post);
2673 
2674  NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(*dest);
2675  if (nwb != NULL) nwb->SetPIP(parts->u.pip.pre, parts->u.pip.inter, parts->u.pip.post);
2676  break;
2677  }
2678 
2679  case WPT_SCROLLBAR: {
2680  NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(*dest);
2681  if (nwc != NULL) {
2682  nwc->scrollbar_index = parts->u.widget.index;
2683  }
2684  break;
2685  }
2686 
2687  case WPT_ENDCONTAINER:
2688  return num_used;
2689 
2690  case NWID_VIEWPORT:
2691  if (*dest != NULL) return num_used;
2692  *dest = new NWidgetViewport(parts->u.widget.index);
2693  *biggest_index = max(*biggest_index, (int)parts->u.widget.index);
2694  break;
2695 
2696  case NWID_HSCROLLBAR:
2697  case NWID_VSCROLLBAR:
2698  if (*dest != NULL) return num_used;
2699  *dest = new NWidgetScrollbar(parts->type, parts->u.widget.colour, parts->u.widget.index);
2700  *biggest_index = max(*biggest_index, (int)parts->u.widget.index);
2701  break;
2702 
2703  case NWID_SELECTION: {
2704  if (*dest != NULL) return num_used;
2705  NWidgetStacked *nws = new NWidgetStacked();
2706  *dest = nws;
2707  *fill_dest = true;
2708  nws->SetIndex(parts->u.widget.index);
2709  *biggest_index = max(*biggest_index, (int)parts->u.widget.index);
2710  break;
2711  }
2712 
2713  default:
2714  if (*dest != NULL) return num_used;
2715  assert((parts->type & WWT_MASK) < WWT_LAST || (parts->type & WWT_MASK) == NWID_BUTTON_DROPDOWN);
2716  *dest = new NWidgetLeaf(parts->type, parts->u.widget.colour, parts->u.widget.index, 0x0, STR_NULL);
2717  *biggest_index = max(*biggest_index, (int)parts->u.widget.index);
2718  break;
2719  }
2720  num_used++;
2721  parts++;
2722  }
2723 
2724  return num_used;
2725 }
2726 
2736 static int MakeWidgetTree(const NWidgetPart *parts, int count, NWidgetBase **parent, int *biggest_index)
2737 {
2738  /* If *parent == NULL, only the first widget is read and returned. Otherwise, *parent must point to either
2739  * a #NWidgetContainer or a #NWidgetBackground object, and parts are added as much as possible. */
2740  NWidgetContainer *nwid_cont = dynamic_cast<NWidgetContainer *>(*parent);
2741  NWidgetBackground *nwid_parent = dynamic_cast<NWidgetBackground *>(*parent);
2742  assert(*parent == NULL || (nwid_cont != NULL && nwid_parent == NULL) || (nwid_cont == NULL && nwid_parent != NULL));
2743 
2744  int total_used = 0;
2745  for (;;) {
2746  NWidgetBase *sub_widget = NULL;
2747  bool fill_sub = false;
2748  int num_used = MakeNWidget(parts, count - total_used, &sub_widget, &fill_sub, biggest_index);
2749  parts += num_used;
2750  total_used += num_used;
2751 
2752  /* Break out of loop when end reached */
2753  if (sub_widget == NULL) break;
2754 
2755  /* If sub-widget is a container, recursively fill that container. */
2756  WidgetType tp = sub_widget->type;
2757  if (fill_sub && (tp == NWID_HORIZONTAL || tp == NWID_HORIZONTAL_LTR || tp == NWID_VERTICAL || tp == NWID_MATRIX
2758  || tp == WWT_PANEL || tp == WWT_FRAME || tp == WWT_INSET || tp == NWID_SELECTION)) {
2759  NWidgetBase *sub_ptr = sub_widget;
2760  int num_used = MakeWidgetTree(parts, count - total_used, &sub_ptr, biggest_index);
2761  parts += num_used;
2762  total_used += num_used;
2763  }
2764 
2765  /* Add sub_widget to parent container if available, otherwise return the widget to the caller. */
2766  if (nwid_cont != NULL) nwid_cont->Add(sub_widget);
2767  if (nwid_parent != NULL) nwid_parent->Add(sub_widget);
2768  if (nwid_cont == NULL && nwid_parent == NULL) {
2769  *parent = sub_widget;
2770  return total_used;
2771  }
2772  }
2773 
2774  if (count == total_used) return total_used; // Reached the end of the array of parts?
2775 
2776  assert(total_used < count);
2777  assert(parts->type == WPT_ENDCONTAINER);
2778  return total_used + 1; // *parts is also 'used'
2779 }
2780 
2792 NWidgetContainer *MakeNWidgets(const NWidgetPart *parts, int count, int *biggest_index, NWidgetContainer *container)
2793 {
2794  *biggest_index = -1;
2795  if (container == NULL) container = new NWidgetVertical();
2796  NWidgetBase *cont_ptr = container;
2797  MakeWidgetTree(parts, count, &cont_ptr, biggest_index);
2798  return container;
2799 }
2800 
2814 NWidgetContainer *MakeWindowNWidgetTree(const NWidgetPart *parts, int count, int *biggest_index, NWidgetStacked **shade_select)
2815 {
2816  *biggest_index = -1;
2817 
2818  /* Read the first widget recursively from the array. */
2819  NWidgetBase *nwid = NULL;
2820  int num_used = MakeWidgetTree(parts, count, &nwid, biggest_index);
2821  assert(nwid != NULL);
2822  parts += num_used;
2823  count -= num_used;
2824 
2825  NWidgetContainer *root = new NWidgetVertical;
2826  root->Add(nwid);
2827  if (count == 0) { // There is no body at all.
2828  *shade_select = NULL;
2829  return root;
2830  }
2831 
2832  /* If the first widget looks like a titlebar, treat it as such.
2833  * If it has a shading box, silently add a shade selection widget in the tree. */
2834  NWidgetHorizontal *hor_cont = dynamic_cast<NWidgetHorizontal *>(nwid);
2835  NWidgetContainer *body;
2836  if (hor_cont != NULL && hor_cont->GetWidgetOfType(WWT_CAPTION) != NULL && hor_cont->GetWidgetOfType(WWT_SHADEBOX) != NULL) {
2837  *shade_select = new NWidgetStacked;
2838  root->Add(*shade_select);
2839  body = new NWidgetVertical;
2840  (*shade_select)->Add(body);
2841  } else {
2842  *shade_select = NULL;
2843  body = root;
2844  }
2845 
2846  /* Load the remaining parts into 'body'. */
2847  int biggest2 = -1;
2848  MakeNWidgets(parts, count, &biggest2, body);
2849 
2850  *biggest_index = max(*biggest_index, biggest2);
2851  return root;
2852 }
2853 
2864 NWidgetBase *MakeCompanyButtonRows(int *biggest_index, int widget_first, int widget_last, int max_length, StringID button_tooltip)
2865 {
2866  assert(max_length >= 1);
2867  NWidgetVertical *vert = NULL; // Storage for all rows.
2868  NWidgetHorizontal *hor = NULL; // Storage for buttons in one row.
2869  int hor_length = 0;
2870 
2871  Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
2872  sprite_size.width += WD_MATRIX_LEFT + WD_MATRIX_RIGHT;
2873  sprite_size.height += WD_MATRIX_TOP + WD_MATRIX_BOTTOM + 1; // 1 for the 'offset' of being pressed
2874 
2875  for (int widnum = widget_first; widnum <= widget_last; widnum++) {
2876  /* Ensure there is room in 'hor' for another button. */
2877  if (hor_length == max_length) {
2878  if (vert == NULL) vert = new NWidgetVertical();
2879  vert->Add(hor);
2880  hor = NULL;
2881  hor_length = 0;
2882  }
2883  if (hor == NULL) {
2884  hor = new NWidgetHorizontal();
2885  hor_length = 0;
2886  }
2887 
2888  NWidgetBackground *panel = new NWidgetBackground(WWT_PANEL, COLOUR_GREY, widnum);
2889  panel->SetMinimalSize(sprite_size.width, sprite_size.height);
2890  panel->SetFill(1, 1);
2891  panel->SetResize(1, 0);
2892  panel->SetDataTip(0x0, button_tooltip);
2893  hor->Add(panel);
2894  hor_length++;
2895  }
2896  *biggest_index = widget_last;
2897  if (vert == NULL) return hor; // All buttons fit in a single row.
2898 
2899  if (hor_length > 0 && hor_length < max_length) {
2900  /* Last row is partial, add a spacer at the end to force all buttons to the left. */
2901  NWidgetSpacer *spc = new NWidgetSpacer(sprite_size.width, sprite_size.height);
2902  spc->SetFill(1, 1);
2903  spc->SetResize(1, 0);
2904  hor->Add(spc);
2905  }
2906  if (hor != NULL) vert->Add(hor);
2907  return vert;
2908 }