OpenTTD Source 20250311-master-g40ddc03423
widget.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
6 */
7
10#include "stdafx.h"
11#include "core/backup_type.hpp"
12#include "company_func.h"
13#include "strings_type.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"
20#include "settings_type.h"
21#include "querystring_gui.h"
22
23#include "table/sprites.h"
24#include "table/strings.h"
26
27#include "safeguards.h"
28
30
31static std::string GetStringForWidget(const Window *w, const NWidgetCore *nwid, bool secondary = false)
32{
33 StringID stringid = nwid->GetString();
34 if (nwid->GetIndex() < 0) {
35 if (stringid == STR_NULL) return {};
36
37 return GetString(stringid + (secondary ? 1 : 0));
38 }
39
40 return w->GetWidgetString(nwid->GetIndex(), stringid + (secondary ? 1 : 0));
41}
42
48static inline RectPadding ScaleGUITrad(const RectPadding &r)
49{
50 return {(uint8_t)ScaleGUITrad(r.left), (uint8_t)ScaleGUITrad(r.top), (uint8_t)ScaleGUITrad(r.right), (uint8_t)ScaleGUITrad(r.bottom)};
51}
52
58static inline Dimension ScaleGUITrad(const Dimension &dim)
59{
60 return {(uint)ScaleGUITrad(dim.width), (uint)ScaleGUITrad(dim.height)};
61}
62
68{
69 Point offset;
70 Dimension d = GetSpriteSize(sprid, &offset, ZOOM_LVL_NORMAL);
71 d.width -= offset.x;
72 d.height -= offset.y;
73 return ScaleGUITrad(d);
74}
75
80{
87 } else {
89 }
104
110}
111
119static inline Point GetAlignedPosition(const Rect &r, const Dimension &d, StringAlignment align)
120{
121 Point p;
122 /* In case we have a RTL language we swap the alignment. */
123 if (!(align & SA_FORCE) && _current_text_dir == TD_RTL && (align & SA_HOR_MASK) != SA_HOR_CENTER) align ^= SA_RIGHT;
124 switch (align & SA_HOR_MASK) {
125 case SA_LEFT: p.x = r.left; break;
126 case SA_HOR_CENTER: p.x = CenterBounds(r.left, r.right, d.width); break;
127 case SA_RIGHT: p.x = r.right + 1 - d.width; break;
128 default: NOT_REACHED();
129 }
130 switch (align & SA_VERT_MASK) {
131 case SA_TOP: p.y = r.top; break;
132 case SA_VERT_CENTER: p.y = CenterBounds(r.top, r.bottom, d.height); break;
133 case SA_BOTTOM: p.y = r.bottom + 1 - d.height; break;
134 default: NOT_REACHED();
135 }
136 return p;
137}
138
148static Point HandleScrollbarHittest(const Scrollbar *sb, int top, int bottom, bool horizontal)
149{
150 /* Base for reversion */
151 int rev_base = top + bottom;
152 int button_size;
153 if (horizontal) {
154 button_size = NWidgetScrollbar::GetHorizontalDimension().width;
155 } else {
156 button_size = NWidgetScrollbar::GetVerticalDimension().height;
157 }
158 top += button_size; // top points to just below the up-button
159 bottom -= button_size; // bottom points to top of the down-button
160
161 int count = sb->GetCount();
162 int cap = sb->GetCapacity();
163
164 if (count > cap) {
165 int height = (bottom - top);
166 int slider_height = std::max(button_size, cap * height / count);
167 height -= slider_height;
168
169 top += height * sb->GetPosition() / (count - cap);
170 bottom = top + slider_height;
171 }
172
173 Point pt;
174 if (horizontal && _current_text_dir == TD_RTL) {
175 pt.x = rev_base - bottom;
176 pt.y = rev_base - top;
177 } else {
178 pt.x = top;
179 pt.y = bottom;
180 }
181 return pt;
182}
183
193static void ScrollbarClickPositioning(Window *w, NWidgetScrollbar *sb, int x, int y, int mi, int ma)
194{
195 int pos;
196 int button_size;
197 bool rtl = false;
198 bool changed = false;
199
200 if (sb->type == NWID_HSCROLLBAR) {
201 pos = x;
202 rtl = _current_text_dir == TD_RTL;
203 button_size = NWidgetScrollbar::GetHorizontalDimension().width;
204 } else {
205 pos = y;
206 button_size = NWidgetScrollbar::GetVerticalDimension().height;
207 }
208 if (pos < mi + button_size) {
209 /* Pressing the upper button? */
211 if (_scroller_click_timeout <= 1) {
212 _scroller_click_timeout = 3;
213 changed = sb->UpdatePosition(rtl ? 1 : -1);
214 }
216 } else if (pos >= ma - button_size) {
217 /* Pressing the lower button? */
219
220 if (_scroller_click_timeout <= 1) {
221 _scroller_click_timeout = 3;
222 changed = sb->UpdatePosition(rtl ? -1 : 1);
223 }
225 } else {
226 Point pt = HandleScrollbarHittest(sb, mi, ma, sb->type == NWID_HSCROLLBAR);
227
228 if (pos < pt.x) {
229 changed = sb->UpdatePosition(rtl ? 1 : -1, Scrollbar::SS_BIG);
230 } else if (pos > pt.y) {
231 changed = sb->UpdatePosition(rtl ? -1 : 1, Scrollbar::SS_BIG);
232 } else {
233 _scrollbar_start_pos = pt.x - mi - button_size;
234 _scrollbar_size = ma - mi - button_size * 2 - (pt.y - pt.x);
236 _cursorpos_drag_start = _cursor.pos;
237 }
238 }
239
240 if (changed) {
241 /* Position changed so refresh the window */
242 w->SetDirty();
243 } else {
244 /* No change so only refresh this scrollbar */
245 sb->SetDirty(w);
246 }
247}
248
257void ScrollbarClickHandler(Window *w, NWidgetCore *nw, int x, int y)
258{
259 int mi, ma;
260
261 if (nw->type == NWID_HSCROLLBAR) {
262 mi = nw->pos_x;
263 ma = nw->pos_x + nw->current_x;
264 } else {
265 mi = nw->pos_y;
266 ma = nw->pos_y + nw->current_y;
267 }
268 NWidgetScrollbar *scrollbar = dynamic_cast<NWidgetScrollbar*>(nw);
269 assert(scrollbar != nullptr);
270 ScrollbarClickPositioning(w, scrollbar, x, y, mi, ma);
271}
272
281WidgetID GetWidgetFromPos(const Window *w, int x, int y)
282{
283 NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
284 return (nw != nullptr) ? nw->GetIndex() : -1;
285}
286
296void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
297{
298 if (flags.Test(FrameFlag::Transparent)) {
299 GfxFillRect(left, top, right, bottom, PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOUR);
300 } else {
301 assert(colour < COLOUR_END);
302
303 const uint dark = GetColourGradient(colour, SHADE_DARK);
304 const uint medium_dark = GetColourGradient(colour, SHADE_LIGHT);
305 const uint medium_light = GetColourGradient(colour, SHADE_LIGHTER);
306 const uint light = GetColourGradient(colour, SHADE_LIGHTEST);
307 uint interior;
308
309 Rect outer = {left, top, right, bottom}; // Outside rectangle
310 Rect inner = outer.Shrink(WidgetDimensions::scaled.bevel); // Inside rectangle
311
312 if (flags.Test(FrameFlag::Lowered)) {
313 GfxFillRect(outer.left, outer.top, inner.left - 1, outer.bottom, dark); // Left
314 GfxFillRect(inner.left, outer.top, outer.right, inner.top - 1, dark); // Top
315 GfxFillRect(inner.right + 1, inner.top, outer.right, inner.bottom, light); // Right
316 GfxFillRect(inner.left, inner.bottom + 1, outer.right, outer.bottom, light); // Bottom
317 interior = (flags.Test(FrameFlag::Darkened) ? medium_dark : medium_light);
318 } else {
319 GfxFillRect(outer.left, outer.top, inner.left - 1, inner.bottom, light); // Left
320 GfxFillRect(inner.left, outer.top, inner.right, inner.top - 1, light); // Top
321 GfxFillRect(inner.right + 1, outer.top, outer.right, inner.bottom, dark); // Right
322 GfxFillRect(outer.left, inner.bottom + 1, outer.right, outer.bottom, dark); // Bottom
323 interior = medium_dark;
324 }
325 if (!flags.Test(FrameFlag::BorderOnly)) {
326 GfxFillRect(inner.left, inner.top, inner.right, inner.bottom, interior); // Inner
327 }
328 }
329}
330
331void DrawSpriteIgnorePadding(SpriteID img, PaletteID pal, const Rect &r, StringAlignment align)
332{
333 Point offset;
334 Dimension d = GetSpriteSize(img, &offset);
335 d.width -= offset.x;
336 d.height -= offset.y;
337
338 Point p = GetAlignedPosition(r, d, align);
339 DrawSprite(img, pal, p.x - offset.x, p.y - offset.y);
340}
341
351static inline void DrawImageButtons(const Rect &r, WidgetType type, Colours colour, bool clicked, SpriteID img, StringAlignment align)
352{
353 assert(img != 0);
354 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
355
356 if ((type & WWT_MASK) == WWT_IMGBTN_2 && clicked) img++; // Show different image when clicked for #WWT_IMGBTN_2.
357 DrawSpriteIgnorePadding(img, PAL_NONE, r, align);
358}
359
368static inline void DrawLabel(const Rect &r, TextColour colour, std::string_view str, StringAlignment align, FontSize fs)
369{
370 if (str.empty()) return;
371
372 Dimension d = GetStringBoundingBox(str, fs);
373 Point p = GetAlignedPosition(r, d, align);
374 DrawString(r.left, r.right, p.y, str, colour, align, false, fs);
375}
376
385static inline void DrawText(const Rect &r, TextColour colour, std::string_view str, StringAlignment align, FontSize fs)
386{
387 if (str.empty()) return;
388
389 Dimension d = GetStringBoundingBox(str, fs);
390 Point p = GetAlignedPosition(r, d, align);
391 DrawString(r.left, r.right, p.y, str, colour, align, false, fs);
392}
393
403static inline void DrawInset(const Rect &r, Colours colour, TextColour text_colour, std::string_view str, StringAlignment align, FontSize fs)
404{
405 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, {FrameFlag::Lowered, FrameFlag::Darkened});
406 if (!str.empty()) DrawString(r.Shrink(WidgetDimensions::scaled.inset), str, text_colour, align, false, fs);
407}
408
419static inline void DrawMatrix(const Rect &r, Colours colour, bool clicked, uint32_t num_columns, uint32_t num_rows, uint resize_x, uint resize_y)
420{
421 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
422
423 int column_width; // Width of a single column in the matrix.
424 if (num_columns == 0) {
425 column_width = resize_x;
426 num_columns = r.Width() / column_width;
427 } else {
428 column_width = r.Width() / num_columns;
429 }
430
431 int row_height; // Height of a single row in the matrix.
432 if (num_rows == 0) {
433 row_height = resize_y;
434 num_rows = r.Height() / row_height;
435 } else {
436 row_height = r.Height() / num_rows;
437 }
438
439 int col = GetColourGradient(colour, SHADE_LIGHTER);
440
441 int x = r.left;
442 for (int ctr = num_columns; ctr > 1; ctr--) {
443 x += column_width;
444 GfxFillRect(x, r.top + WidgetDimensions::scaled.bevel.top, x + WidgetDimensions::scaled.bevel.left - 1, r.bottom - WidgetDimensions::scaled.bevel.bottom, col);
445 }
446
447 x = r.top;
448 for (int ctr = num_rows; ctr > 1; ctr--) {
449 x += row_height;
451 }
452
453 col = GetColourGradient(colour, SHADE_NORMAL);
454
455 x = r.left - 1;
456 for (int ctr = num_columns; ctr > 1; ctr--) {
457 x += column_width;
458 GfxFillRect(x - WidgetDimensions::scaled.bevel.right + 1, r.top + WidgetDimensions::scaled.bevel.top, x, r.bottom - WidgetDimensions::scaled.bevel.bottom, col);
459 }
460
461 x = r.top - 1;
462 for (int ctr = num_rows; ctr > 1; ctr--) {
463 x += row_height;
464 GfxFillRect(r.left + WidgetDimensions::scaled.bevel.left, x - WidgetDimensions::scaled.bevel.bottom + 1, r.right - WidgetDimensions::scaled.bevel.right, x, col);
465 }
466}
467
477static inline void DrawVerticalScrollbar(const Rect &r, Colours colour, bool up_clicked, bool bar_dragged, bool down_clicked, const Scrollbar *scrollbar)
478{
479 int height = NWidgetScrollbar::GetVerticalDimension().height;
480
481 /* draw up/down buttons */
482 DrawImageButtons(r.WithHeight(height, false), NWID_VSCROLLBAR, colour, up_clicked, SPR_ARROW_UP, SA_CENTER);
483 DrawImageButtons(r.WithHeight(height, true), NWID_VSCROLLBAR, colour, down_clicked, SPR_ARROW_DOWN, SA_CENTER);
484
485 int c1 = GetColourGradient(colour, SHADE_DARK);
486 int c2 = GetColourGradient(colour, SHADE_LIGHTEST);
487
488 /* draw "shaded" background */
489 GfxFillRect(r.left, r.top + height, r.right, r.bottom - height, c2);
490 GfxFillRect(r.left, r.top + height, r.right, r.bottom - height, c1, FILLRECT_CHECKER);
491
492 /* track positions. These fractions are based on original 1x dimensions, but scale better. */
493 int left = r.left + r.Width() * 3 / 11; /* left track is positioned 3/11ths from the left */
494 int right = r.left + r.Width() * 8 / 11; /* right track is positioned 8/11ths from the left */
495 const uint8_t bl = WidgetDimensions::scaled.bevel.left;
496 const uint8_t br = WidgetDimensions::scaled.bevel.right;
497
498 /* draw shaded lines */
499 GfxFillRect(left - bl, r.top + height, left - 1, r.bottom - height, c1);
500 GfxFillRect(left, r.top + height, left + br - 1, r.bottom - height, c2);
501 GfxFillRect(right - bl, r.top + height, right - 1, r.bottom - height, c1);
502 GfxFillRect(right, r.top + height, right + br - 1, r.bottom - height, c2);
503
504 Point pt = HandleScrollbarHittest(scrollbar, r.top, r.bottom, false);
505 DrawFrameRect(r.left, pt.x, r.right, pt.y, colour, bar_dragged ? FrameFlag::Lowered : FrameFlags{});
506}
507
517static inline void DrawHorizontalScrollbar(const Rect &r, Colours colour, bool left_clicked, bool bar_dragged, bool right_clicked, const Scrollbar *scrollbar)
518{
519 int width = NWidgetScrollbar::GetHorizontalDimension().width;
520
521 DrawImageButtons(r.WithWidth(width, false), NWID_HSCROLLBAR, colour, left_clicked, SPR_ARROW_LEFT, SA_CENTER);
522 DrawImageButtons(r.WithWidth(width, true), NWID_HSCROLLBAR, colour, right_clicked, SPR_ARROW_RIGHT, SA_CENTER);
523
524 int c1 = GetColourGradient(colour, SHADE_DARK);
525 int c2 = GetColourGradient(colour, SHADE_LIGHTEST);
526
527 /* draw "shaded" background */
528 GfxFillRect(r.left + width, r.top, r.right - width, r.bottom, c2);
529 GfxFillRect(r.left + width, r.top, r.right - width, r.bottom, c1, FILLRECT_CHECKER);
530
531 /* track positions. These fractions are based on original 1x dimensions, but scale better. */
532 int top = r.top + r.Height() * 3 / 11; /* top track is positioned 3/11ths from the top */
533 int bottom = r.top + r.Height() * 8 / 11; /* bottom track is positioned 8/11ths from the top */
534 const uint8_t bt = WidgetDimensions::scaled.bevel.top;
535 const uint8_t bb = WidgetDimensions::scaled.bevel.bottom;
536
537 /* draw shaded lines */
538 GfxFillRect(r.left + width, top - bt, r.right - width, top - 1, c1);
539 GfxFillRect(r.left + width, top, r.right - width, top + bb - 1, c2);
540 GfxFillRect(r.left + width, bottom - bt, r.right - width, bottom - 1, c1);
541 GfxFillRect(r.left + width, bottom, r.right - width, bottom + bb - 1, c2);
542
543 /* draw actual scrollbar */
544 Point pt = HandleScrollbarHittest(scrollbar, r.left, r.right, true);
545 DrawFrameRect(pt.x, r.top, pt.y, r.bottom, colour, bar_dragged ? FrameFlag::Lowered : FrameFlags{});
546}
547
557static inline void DrawFrame(const Rect &r, Colours colour, TextColour text_colour, std::string_view str, StringAlignment align, FontSize fs)
558{
559 int x2 = r.left; // by default the left side is the left side of the widget
560
561 if (!str.empty()) x2 = DrawString(r.left + WidgetDimensions::scaled.frametext.left, r.right - WidgetDimensions::scaled.frametext.right, r.top, str, text_colour, align, false, fs);
562
563 int c1 = GetColourGradient(colour, SHADE_DARK);
564 int c2 = GetColourGradient(colour, SHADE_LIGHTEST);
565
566 /* If the frame has text, adjust the top bar to fit half-way through */
567 Rect inner = r.Shrink(ScaleGUITrad(1));
568 if (!str.empty()) inner.top = r.top + GetCharacterHeight(FS_NORMAL) / 2;
569
570 Rect outer = inner.Expand(WidgetDimensions::scaled.bevel);
571 Rect inside = inner.Shrink(WidgetDimensions::scaled.bevel);
572
573 if (_current_text_dir == TD_LTR) {
574 /* Line from upper left corner to start of text */
575 GfxFillRect(outer.left, outer.top, r.left + WidgetDimensions::scaled.frametext.left - WidgetDimensions::scaled.bevel.left - 1, inner.top - 1, c1);
576 GfxFillRect(inner.left, inner.top, r.left + WidgetDimensions::scaled.frametext.left - WidgetDimensions::scaled.bevel.left - 1, inside.top - 1, c2);
577
578 /* Line from end of text to upper right corner */
579 GfxFillRect(x2 + WidgetDimensions::scaled.bevel.right, outer.top, inner.right, inner.top - 1, c1);
580 GfxFillRect(x2 + WidgetDimensions::scaled.bevel.right, inner.top, inside.right, inside.top - 1, c2);
581 } else {
582 /* Line from upper left corner to start of text */
583 GfxFillRect(outer.left, outer.top, x2 - WidgetDimensions::scaled.bevel.left - 1, inner.top - 1, c1);
584 GfxFillRect(inner.left, inner.top, x2 - WidgetDimensions::scaled.bevel.left - 1, inside.top - 1, c2);
585
586 /* Line from end of text to upper right corner */
587 GfxFillRect(r.right - WidgetDimensions::scaled.frametext.right + WidgetDimensions::scaled.bevel.right, outer.top, inner.right, inner.top - 1, c1);
588 GfxFillRect(r.right - WidgetDimensions::scaled.frametext.right + WidgetDimensions::scaled.bevel.right, inner.top, inside.right, inside.top - 1, c2);
589 }
590
591 /* Line from upper left corner to bottom left corner */
592 GfxFillRect(outer.left, inner.top, inner.left - 1, inner.bottom, c1);
593 GfxFillRect(inner.left, inside.top, inside.left - 1, inside.bottom, c2);
594
595 /* Line from upper right corner to bottom right corner */
596 GfxFillRect(inside.right + 1, inner.top, inner.right, inside.bottom, c1);
597 GfxFillRect(inner.right + 1, outer.top, outer.right, inner.bottom, c2);
598
599 /* Line from bottom left corner to bottom right corner */
600 GfxFillRect(inner.left, inside.bottom + 1, inner.right, inner.bottom, c1);
601 GfxFillRect(outer.left, inner.bottom + 1, outer.right, outer.bottom, c2);
602}
603
610static inline void DrawShadeBox(const Rect &r, Colours colour, bool clicked)
611{
612 DrawImageButtons(r, WWT_SHADEBOX, colour, clicked, clicked ? SPR_WINDOW_SHADE: SPR_WINDOW_UNSHADE, SA_CENTER);
613}
614
621static inline void DrawStickyBox(const Rect &r, Colours colour, bool clicked)
622{
623 DrawImageButtons(r, WWT_STICKYBOX, colour, clicked, clicked ? SPR_PIN_UP : SPR_PIN_DOWN, SA_CENTER);
624}
625
632static inline void DrawDefSizeBox(const Rect &r, Colours colour, bool clicked)
633{
634 DrawImageButtons(r, WWT_DEFSIZEBOX, colour, clicked, SPR_WINDOW_DEFSIZE, SA_CENTER);
635}
636
643static inline void DrawDebugBox(const Rect &r, Colours colour, bool clicked)
644{
645 DrawImageButtons(r, WWT_DEBUGBOX, colour, clicked, SPR_WINDOW_DEBUG, SA_CENTER);
646}
647
656static inline void DrawResizeBox(const Rect &r, Colours colour, bool at_left, bool clicked, bool bevel)
657{
658 if (bevel) {
659 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
660 } else if (clicked) {
662 }
663 DrawSpriteIgnorePadding(at_left ? SPR_WINDOW_RESIZE_LEFT : SPR_WINDOW_RESIZE_RIGHT, PAL_NONE, r.Shrink(ScaleGUITrad(2)), at_left ? (SA_LEFT | SA_BOTTOM | SA_FORCE) : (SA_RIGHT | SA_BOTTOM | SA_FORCE));
664}
665
671static inline void DrawCloseBox(const Rect &r, Colours colour)
672{
673 if (colour != COLOUR_WHITE) DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, {});
674 Point offset;
675 Dimension d = GetSpriteSize(SPR_CLOSEBOX, &offset);
676 d.width -= offset.x;
677 d.height -= offset.y;
678 int s = ScaleSpriteTrad(1); /* Offset to account for shadow of SPR_CLOSEBOX */
679 DrawSprite(SPR_CLOSEBOX, (colour != COLOUR_WHITE ? TC_BLACK : TC_SILVER) | (1U << PALETTE_TEXT_RECOLOUR), CenterBounds(r.left, r.right, d.width - s) - offset.x, CenterBounds(r.top, r.bottom, d.height - s) - offset.y);
680}
681
692void DrawCaption(const Rect &r, Colours colour, Owner owner, TextColour text_colour, std::string_view str, StringAlignment align, FontSize fs)
693{
694 bool company_owned = owner < MAX_COMPANIES;
695
699
700 if (company_owned) {
702 }
703
704 if (str.empty()) return;
705
707 Point p = GetAlignedPosition(r, d, align);
708 DrawString(r.left + WidgetDimensions::scaled.captiontext.left, r.right - WidgetDimensions::scaled.captiontext.left, p.y, str, text_colour, align, false, fs);
709}
710
722static inline void DrawButtonDropdown(const Rect &r, Colours colour, bool clicked_button, bool clicked_dropdown, std::string_view str, StringAlignment align)
723{
724 int dd_width = NWidgetLeaf::dropdown_dimension.width;
725
726 if (_current_text_dir == TD_LTR) {
727 DrawFrameRect(r.left, r.top, r.right - dd_width, r.bottom, colour, clicked_button ? FrameFlag::Lowered : FrameFlags{});
728 DrawImageButtons(r.WithWidth(dd_width, true), WWT_DROPDOWN, colour, clicked_dropdown, SPR_ARROW_DOWN, SA_CENTER);
729 if (!str.empty()) {
730 DrawString(r.left + WidgetDimensions::scaled.dropdowntext.left, r.right - dd_width - WidgetDimensions::scaled.dropdowntext.right, CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), str, TC_BLACK, align);
731 }
732 } else {
733 DrawFrameRect(r.left + dd_width, r.top, r.right, r.bottom, colour, clicked_button ? FrameFlag::Lowered : FrameFlags{});
734 DrawImageButtons(r.WithWidth(dd_width, false), WWT_DROPDOWN, colour, clicked_dropdown, SPR_ARROW_DOWN, SA_CENTER);
735 if (!str.empty()) {
736 DrawString(r.left + dd_width + WidgetDimensions::scaled.dropdowntext.left, r.right - WidgetDimensions::scaled.dropdowntext.right, CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), str, TC_BLACK, align);
737 }
738 }
739}
740
745{
746 this->nested_root->Draw(this);
747
749 DrawFrameRect(0, 0, this->width - 1, this->height - 1, COLOUR_WHITE, FrameFlag::BorderOnly);
750 }
751
753 extern bool _window_highlight_colour;
754 for (const auto &pair : this->widget_lookup) {
755 const NWidgetBase *widget = pair.second;
756 if (!widget->IsHighlighted()) continue;
757
758 Rect outer = widget->GetCurrentRect();
760
761 int colour = _string_colourmap[_window_highlight_colour ? widget->GetHighlightColour() : TC_WHITE];
762
763 GfxFillRect(outer.left, outer.top, inner.left, inner.bottom, colour);
764 GfxFillRect(inner.left + 1, outer.top, inner.right - 1, inner.top, colour);
765 GfxFillRect(inner.right, outer.top, outer.right, inner.bottom, colour);
766 GfxFillRect(outer.left + 1, inner.bottom, outer.right - 1, outer.bottom, colour);
767 }
768 }
769}
770
777{
778 if (state == SBS_OFF) return;
779
780 assert(!this->widget_lookup.empty());
781 Rect r = this->GetWidget<NWidgetBase>(widget)->GetCurrentRect();
782
783 /* Sort button uses the same sprites as vertical scrollbar */
784 Dimension dim = NWidgetScrollbar::GetVerticalDimension();
785
786 DrawSpriteIgnorePadding(state == SBS_DOWN ? SPR_ARROW_DOWN : SPR_ARROW_UP, PAL_NONE, r.WithWidth(dim.width, _current_text_dir == TD_LTR), SA_CENTER);
787}
788
794{
795 return NWidgetScrollbar::GetVerticalDimension().width + 1;
796}
797
798bool _draw_widget_outlines;
799
800static void DrawOutline(const Window *, const NWidgetBase *wid)
801{
802 if (!_draw_widget_outlines || wid->current_x == 0 || wid->current_y == 0) return;
803
804 DrawRectOutline(wid->GetCurrentRect(), PC_WHITE, 1, 4);
805}
806
905void NWidgetBase::SetDirty(const Window *w) const
906{
907 int abs_left = w->left + this->pos_x;
908 int abs_top = w->top + this->pos_y;
909 AddDirtyBlock(abs_left, abs_top, abs_left + this->current_x, abs_top + this->current_y);
910}
911
926{
927 return (this->type == tp) ? this : nullptr;
928}
929
930void NWidgetBase::ApplyAspectRatio()
931{
932 if (this->aspect_ratio == 0) return;
933 if (this->smallest_x == 0 || this->smallest_y == 0) return;
934
935 uint x = this->smallest_x;
936 uint y = this->smallest_y;
937 if (this->aspect_flags.Test(AspectFlag::ResizeX)) x = std::max(this->smallest_x, static_cast<uint>(this->smallest_y * std::abs(this->aspect_ratio)));
938 if (this->aspect_flags.Test(AspectFlag::ResizeY)) y = std::max(this->smallest_y, static_cast<uint>(this->smallest_x / std::abs(this->aspect_ratio)));
939
940 this->smallest_x = x;
941 this->smallest_y = y;
942}
943
944void NWidgetBase::AdjustPaddingForZoom()
945{
946 this->padding = ScaleGUITrad(this->uz_padding);
947}
948
956{
957 this->fill_x = fill_x;
958 this->fill_y = fill_y;
959}
960
967{
968 this->aspect_ratio = ratio;
969 this->aspect_flags = flags;
970}
971
978void NWidgetResizeBase::SetAspect(int x_ratio, int y_ratio, AspectFlags flags)
979{
980 this->SetAspect(static_cast<float>(x_ratio) / static_cast<float>(y_ratio), flags);
981}
982
983void NWidgetResizeBase::AdjustPaddingForZoom()
984{
985 if (!this->absolute) {
986 this->min_x = ScaleGUITrad(this->uz_min_x);
988 }
989 NWidgetBase::AdjustPaddingForZoom();
990}
991
997void NWidgetResizeBase::SetMinimalSize(uint min_x, uint min_y)
998{
999 this->uz_min_x = std::max(this->uz_min_x, min_x);
1000 this->uz_min_y = std::max(this->uz_min_y, min_y);
1001 this->min_x = ScaleGUITrad(this->uz_min_x);
1002 this->min_y = std::max(ScaleGUITrad(this->uz_min_y), this->uz_text_lines * GetCharacterHeight(this->uz_text_size) + ScaleGUITrad(this->uz_text_spacing));
1003}
1004
1011{
1012 this->absolute = true;
1013 this->min_x = std::max(this->min_x, min_x);
1014 this->min_y = std::max(this->min_y, min_y);
1015}
1016
1023void NWidgetResizeBase::SetMinimalTextLines(uint8_t min_lines, uint8_t spacing, FontSize size)
1024{
1025 this->uz_text_lines = min_lines;
1026 this->uz_text_spacing = spacing;
1027 this->uz_text_size = size;
1028 this->min_y = std::max(ScaleGUITrad(this->uz_min_y), this->uz_text_lines * GetCharacterHeight(this->uz_text_size) + ScaleGUITrad(this->uz_text_spacing));
1029}
1030
1036void NWidgetResizeBase::SetFill(uint fill_x, uint fill_y)
1037{
1038 this->fill_x = fill_x;
1039 this->fill_y = fill_y;
1040}
1041
1047void NWidgetResizeBase::SetResize(uint resize_x, uint resize_y)
1048{
1049 this->resize_x = resize_x;
1050 this->resize_y = resize_y;
1051}
1052
1060bool NWidgetResizeBase::UpdateMultilineWidgetSize(const std::string &str, int max_lines)
1061{
1062 int y = GetStringHeight(str, this->current_x);
1063 if (y > max_lines * GetCharacterHeight(FS_NORMAL)) {
1064 /* Text at the current width is too tall, so try to guess a better width. */
1066 d.height *= max_lines;
1067 d.width /= 2;
1068 return this->UpdateSize(d.width, d.height);
1069 }
1070 return this->UpdateVerticalSize(y);
1071}
1072
1080bool NWidgetResizeBase::UpdateSize(uint min_x, uint min_y)
1081{
1082 if (min_x == this->min_x && min_y == this->min_y) return false;
1083 this->min_x = min_x;
1084 this->min_y = min_y;
1085 return true;
1086}
1087
1095{
1096 if (min_y == this->min_y) return false;
1097 this->min_y = min_y;
1098 return true;
1099}
1100
1101void NWidgetResizeBase::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool)
1102{
1103 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1104}
1105
1116NWidgetCore::NWidgetCore(WidgetType tp, Colours colour, WidgetID index, uint fill_x, uint fill_y, const WidgetData &widget_data, StringID tool_tip) : NWidgetResizeBase(tp, fill_x, fill_y), index(index)
1117{
1118 this->colour = colour;
1119 this->widget_data = widget_data;
1120 this->SetToolTip(tool_tip);
1121 this->text_colour = tp == WWT_CAPTION ? TC_WHITE : TC_BLACK;
1122}
1123
1129{
1130 this->widget_data.string = string;
1131}
1132
1139{
1140 this->SetString(string);
1141 this->SetToolTip(tool_tip);
1142}
1143
1149{
1150 this->widget_data.sprite = sprite;
1151}
1152
1159{
1160 this->SetSprite(sprite);
1161 this->SetToolTip(tool_tip);
1162}
1163
1169void NWidgetCore::SetMatrixDimension(uint32_t columns, uint32_t rows)
1170{
1171 this->widget_data.matrix = { columns, rows };
1172}
1173
1179{
1180 this->widget_data.resize_widget_type = type;
1181}
1182
1189{
1190 this->text_colour = colour;
1191 this->text_size = size;
1192}
1193
1199{
1200 this->tool_tip = tool_tip;
1201}
1202
1208{
1209 return this->tool_tip;
1210}
1211
1217{
1218 this->align = align;
1219}
1220
1226{
1227 return this->widget_data.string;
1228}
1229
1235{
1236 return this->index;
1237}
1238
1244{
1245 return this->scrollbar_index;
1246}
1247
1249{
1250 if (this->index >= 0) widget_lookup[this->index] = this;
1251}
1252
1254{
1255 return (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) ? this : nullptr;
1256}
1257
1259{
1260 if (this->type == tp) return this;
1261 for (const auto &child_wid : this->children) {
1262 NWidgetBase *nwid = child_wid->GetWidgetOfType(tp);
1263 if (nwid != nullptr) return nwid;
1264 }
1265 return nullptr;
1266}
1267
1268void NWidgetContainer::AdjustPaddingForZoom()
1269{
1270 for (const auto &child_wid : this->children) {
1271 child_wid->AdjustPaddingForZoom();
1272 }
1273 NWidgetBase::AdjustPaddingForZoom();
1274}
1275
1280void NWidgetContainer::Add(std::unique_ptr<NWidgetBase> &&wid)
1281{
1282 assert(wid != nullptr);
1283 wid->parent = this;
1284 this->children.push_back(std::move(wid));
1285}
1286
1288{
1289 for (const auto &child_wid : this->children) {
1290 child_wid->FillWidgetLookup(widget_lookup);
1291 }
1292}
1293
1295{
1296 for (const auto &child_wid : this->children) {
1297 child_wid->Draw(w);
1298 }
1299
1300 DrawOutline(w, this);
1301}
1302
1304{
1305 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
1306
1307 for (const auto &child_wid : this->children) {
1308 NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
1309 if (nwid != nullptr) return nwid;
1310 }
1311 return nullptr;
1312}
1313
1315{
1316 /* Zero size plane selected */
1317 if (this->shown_plane >= SZSP_BEGIN) {
1318 Dimension size = {0, 0};
1319 Dimension padding = {0, 0};
1320 Dimension fill = {(this->shown_plane == SZSP_HORIZONTAL), (this->shown_plane == SZSP_VERTICAL)};
1321 Dimension resize = {(this->shown_plane == SZSP_HORIZONTAL), (this->shown_plane == SZSP_VERTICAL)};
1322 /* Here we're primarily interested in the value of resize */
1323 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
1324
1325 this->smallest_x = size.width;
1326 this->smallest_y = size.height;
1327 this->fill_x = fill.width;
1328 this->fill_y = fill.height;
1329 this->resize_x = resize.width;
1330 this->resize_y = resize.height;
1331 this->ApplyAspectRatio();
1332 return;
1333 }
1334
1335 /* First sweep, recurse down and compute minimal size and filling. */
1336 this->smallest_x = 0;
1337 this->smallest_y = 0;
1338 this->fill_x = this->IsEmpty() ? 0 : 1;
1339 this->fill_y = this->IsEmpty() ? 0 : 1;
1340 this->resize_x = this->IsEmpty() ? 0 : 1;
1341 this->resize_y = this->IsEmpty() ? 0 : 1;
1342 for (const auto &child_wid : this->children) {
1343 child_wid->SetupSmallestSize(w);
1344
1345 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1346 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1347 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1348 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1349 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1350 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1351 this->ApplyAspectRatio();
1352 }
1353}
1354
1355void NWidgetStacked::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1356{
1357 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1358 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1359
1360 if (this->shown_plane >= SZSP_BEGIN) return;
1361
1362 for (const auto &child_wid : this->children) {
1363 uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1364 uint child_width = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1365 uint child_pos_x = (rtl ? child_wid->padding.right : child_wid->padding.left);
1366
1367 uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1368 uint child_height = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1369 uint child_pos_y = child_wid->padding.top;
1370
1371 child_wid->AssignSizePosition(sizing, x + child_pos_x, y + child_pos_y, child_width, child_height, rtl);
1372 }
1373}
1374
1376{
1377 /* We need to update widget_lookup later. */
1378 this->widget_lookup = &widget_lookup;
1379
1380 if (this->index >= 0) widget_lookup[this->index] = this;
1382 /* In case widget IDs are repeated, make sure Window::GetWidget works on displayed widgets. */
1383 if (static_cast<size_t>(this->shown_plane) < this->children.size()) this->children[shown_plane]->FillWidgetLookup(widget_lookup);
1384}
1385
1387{
1388 if (this->shown_plane >= SZSP_BEGIN) return;
1389
1390 assert(static_cast<size_t>(this->shown_plane) < this->children.size());
1391 this->children[shown_plane]->Draw(w);
1392 DrawOutline(w, this);
1393}
1394
1396{
1397 if (this->shown_plane >= SZSP_BEGIN) return nullptr;
1398
1399 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
1400
1401 if (static_cast<size_t>(this->shown_plane) >= this->children.size()) return nullptr;
1402 return this->children[shown_plane]->GetWidgetFromPos(x, y);
1403}
1404
1411{
1412 if (this->shown_plane == plane) return false;
1413 this->shown_plane = plane;
1414 /* In case widget IDs are repeated, make sure Window::GetWidget works on displayed widgets. */
1415 if (static_cast<size_t>(this->shown_plane) < this->children.size()) this->children[shown_plane]->FillWidgetLookup(*this->widget_lookup);
1416 return true;
1417}
1418
1420public:
1422
1423 void SetupSmallestSize(Window *w) override;
1424 void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override;
1425
1426 void Draw(const Window *w) override;
1427
1429};
1430
1432{
1433 /* First sweep, recurse down and compute minimal size and filling. */
1434 this->smallest_x = 0;
1435 this->smallest_y = 0;
1436 this->fill_x = this->IsEmpty() ? 0 : 1;
1437 this->fill_y = this->IsEmpty() ? 0 : 1;
1438 this->resize_x = this->IsEmpty() ? 0 : 1;
1439 this->resize_y = this->IsEmpty() ? 0 : 1;
1440 for (const auto &child_wid : this->children) {
1441 child_wid->SetupSmallestSize(w);
1442
1443 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1444 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1445 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1446 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1447 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1448 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1449 this->ApplyAspectRatio();
1450 }
1451}
1452
1453void NWidgetLayer::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1454{
1455 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1456 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1457
1458 for (const auto &child_wid : this->children) {
1459 uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1460 uint child_width = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1461 uint child_pos_x = (rtl ? child_wid->padding.right : child_wid->padding.left);
1462
1463 uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1464 uint child_height = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1465 uint child_pos_y = child_wid->padding.top;
1466
1467 child_wid->AssignSizePosition(sizing, x + child_pos_x, y + child_pos_y, child_width, child_height, rtl);
1468 }
1469}
1470
1472{
1473 /* Draw in reverse order, as layers are arranged top-down. */
1474 for (auto it = std::rbegin(this->children); it != std::rend(this->children); ++it) {
1475 (*it)->Draw(w);
1476 }
1477
1478 DrawOutline(w, this);
1479}
1480
1481NWidgetPIPContainer::NWidgetPIPContainer(WidgetType tp, NWidContainerFlags flags) : NWidgetContainer(tp)
1482{
1483 this->flags = flags;
1484}
1485
1486void NWidgetPIPContainer::AdjustPaddingForZoom()
1487{
1488 this->pip_pre = ScaleGUITrad(this->uz_pip_pre);
1489 this->pip_inter = ScaleGUITrad(this->uz_pip_inter);
1490 this->pip_post = ScaleGUITrad(this->uz_pip_post);
1491 NWidgetContainer::AdjustPaddingForZoom();
1492}
1493
1503void NWidgetPIPContainer::SetPIP(uint8_t pip_pre, uint8_t pip_inter, uint8_t pip_post)
1504{
1505 this->uz_pip_pre = pip_pre;
1506 this->uz_pip_inter = pip_inter;
1507 this->uz_pip_post = pip_post;
1508
1509 this->pip_pre = ScaleGUITrad(this->uz_pip_pre);
1510 this->pip_inter = ScaleGUITrad(this->uz_pip_inter);
1511 this->pip_post = ScaleGUITrad(this->uz_pip_post);
1512}
1513
1523void NWidgetPIPContainer::SetPIPRatio(uint8_t pip_ratio_pre, uint8_t pip_ratio_inter, uint8_t pip_ratio_post)
1524{
1525 this->pip_ratio_pre = pip_ratio_pre;
1526 this->pip_ratio_inter = pip_ratio_inter;
1527 this->pip_ratio_post = pip_ratio_post;
1528}
1529
1534
1536{
1537 this->smallest_x = 0; // Sum of minimal size of all children.
1538 this->smallest_y = 0; // Biggest child.
1539 this->fill_x = 0; // smallest non-zero child widget fill step.
1540 this->fill_y = 1; // smallest common child fill step.
1541 this->resize_x = 0; // smallest non-zero child widget resize step.
1542 this->resize_y = 1; // smallest common child resize step.
1543 this->gaps = 0;
1544
1545 /* 1a. Forward call, collect longest/widest child length. */
1546 uint longest = 0; // Longest child found.
1547 uint max_vert_fill = 0; // Biggest vertical fill step.
1548 for (const auto &child_wid : this->children) {
1549 child_wid->SetupSmallestSize(w);
1550 longest = std::max(longest, child_wid->smallest_x);
1551 max_vert_fill = std::max(max_vert_fill, child_wid->GetVerticalStepSize(ST_SMALLEST));
1552 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1553 if (child_wid->smallest_x != 0 || child_wid->fill_x != 0) this->gaps++;
1554 }
1555 if (this->gaps > 0) this->gaps--; // Number of gaps is number of widgets less one.
1556 /* 1b. Make the container higher if needed to accommodate all children nicely. */
1557 [[maybe_unused]] uint max_smallest = this->smallest_y + 3 * max_vert_fill; // Upper limit to computing smallest height.
1558 uint cur_height = this->smallest_y;
1559 for (;;) {
1560 for (const auto &child_wid : this->children) {
1561 uint step_size = child_wid->GetVerticalStepSize(ST_SMALLEST);
1562 uint child_height = child_wid->smallest_y + child_wid->padding.Vertical();
1563 if (step_size > 1 && child_height < cur_height) { // Small step sizes or already fitting children are not interesting.
1564 uint remainder = (cur_height - child_height) % step_size;
1565 if (remainder > 0) { // Child did not fit entirely, widen the container.
1566 cur_height += step_size - remainder;
1567 assert(cur_height < max_smallest); // Safeguard against infinite height expansion.
1568 /* Remaining children will adapt to the new cur_height, thus speeding up the computation. */
1569 }
1570 }
1571 }
1572 if (this->smallest_y == cur_height) break;
1573 this->smallest_y = cur_height; // Smallest height got changed, try again.
1574 }
1575 /* 2. For containers that must maintain equal width, extend child minimal size. */
1576 for (const auto &child_wid : this->children) {
1577 child_wid->smallest_y = this->smallest_y - child_wid->padding.Vertical();
1578 child_wid->ApplyAspectRatio();
1579 longest = std::max(longest, child_wid->smallest_x);
1580 }
1582 for (const auto &child_wid : this->children) {
1583 if (child_wid->fill_x == 1) child_wid->smallest_x = longest;
1584 }
1585 }
1586 /* 3. Compute smallest, fill, and resize values of the container. */
1587 for (const auto &child_wid : this->children) {
1588 this->smallest_x += child_wid->smallest_x + child_wid->padding.Horizontal();
1589 if (child_wid->fill_x > 0) {
1590 if (this->fill_x == 0 || this->fill_x > child_wid->fill_x) this->fill_x = child_wid->fill_x;
1591 }
1592 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1593
1594 if (child_wid->resize_x > 0) {
1595 if (this->resize_x == 0 || this->resize_x > child_wid->resize_x) this->resize_x = child_wid->resize_x;
1596 }
1597 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1598 }
1599 if (this->fill_x == 0 && this->pip_ratio_pre + this->pip_ratio_inter + this->pip_ratio_post > 0) this->fill_x = 1;
1600 /* 4. Increase by required PIP space. */
1601 this->smallest_x += this->pip_pre + this->gaps * this->pip_inter + this->pip_post;
1602}
1603
1604void NWidgetHorizontal::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1605{
1606 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1607
1608 /* Compute additional width given to us. */
1609 uint additional_length = given_width - (this->pip_pre + this->gaps * this->pip_inter + this->pip_post);
1610 for (const auto &child_wid : this->children) {
1611 if (child_wid->smallest_x != 0 || child_wid->fill_x != 0) additional_length -= child_wid->smallest_x + child_wid->padding.Horizontal();
1612 }
1613
1614 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1615
1616 /* In principle, the additional horizontal space is distributed evenly over the available resizable children. Due to step sizes, this may not always be feasible.
1617 * To make resizing work as good as possible, first children with biggest step sizes are done. These may get less due to rounding down.
1618 * 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
1619 * of the child with the smallest non-zero stepsize.
1620 *
1621 * 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
1622 * size and position, and directly call child->AssignSizePosition() with the computed values.
1623 * 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
1624 * then we call the child.
1625 */
1626
1627 /* First loop: Find biggest stepsize, find number of children that want a piece of the pie, handle vertical size for all children,
1628 * handle horizontal size for non-resizing children.
1629 */
1630 int num_changing_childs = 0; // Number of children that can change size.
1631 uint biggest_stepsize = 0;
1632 for (const auto &child_wid : this->children) {
1633 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1634 if (hor_step > 0) {
1635 if (!flags.Test(NWidContainerFlag::BigFirst)) num_changing_childs++;
1636 biggest_stepsize = std::max(biggest_stepsize, hor_step);
1637 } else {
1638 child_wid->current_x = child_wid->smallest_x;
1639 }
1640
1641 uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1642 child_wid->current_y = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1643 }
1644
1645 /* First.5 loop: count how many children are of the biggest step size. */
1646 if (flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1647 for (const auto &child_wid : this->children) {
1648 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1649 if (hor_step == biggest_stepsize) {
1650 num_changing_childs++;
1651 }
1652 }
1653 }
1654
1655 /* Second loop: Allocate the additional horizontal space over the resizing children, starting with the biggest resize steps. */
1656 while (biggest_stepsize > 0) {
1657 uint next_biggest_stepsize = 0;
1658 for (const auto &child_wid : this->children) {
1659 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1660 if (hor_step > biggest_stepsize) continue; // Already done
1661 if (hor_step == biggest_stepsize) {
1662 uint increment = additional_length / num_changing_childs;
1663 num_changing_childs--;
1664 if (hor_step > 1) increment -= increment % hor_step;
1665 child_wid->current_x = child_wid->smallest_x + increment;
1666 additional_length -= increment;
1667 continue;
1668 }
1669 next_biggest_stepsize = std::max(next_biggest_stepsize, hor_step);
1670 }
1671 biggest_stepsize = next_biggest_stepsize;
1672
1673 if (num_changing_childs == 0 && flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1674 /* Second.5 loop: count how many children are of the updated biggest step size. */
1675 for (const auto &child_wid : this->children) {
1676 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1677 if (hor_step == biggest_stepsize) {
1678 num_changing_childs++;
1679 }
1680 }
1681 }
1682 }
1683 assert(num_changing_childs == 0);
1684
1685 uint pre = this->pip_pre;
1686 uint inter = this->pip_inter;
1687
1688 if (additional_length > 0) {
1689 /* Allocate remaining space by pip ratios. If this doesn't round exactly, the unused space will fall into pip_post
1690 * which is never explicitly needed. */
1691 int r = this->pip_ratio_pre + this->gaps * this->pip_ratio_inter + this->pip_ratio_post;
1692 if (r > 0) {
1693 pre += this->pip_ratio_pre * additional_length / r;
1694 if (this->gaps > 0) inter += this->pip_ratio_inter * additional_length / r;
1695 }
1696 }
1697
1698 /* Third loop: Compute position and call the child. */
1699 uint position = rtl ? this->current_x - pre : pre; // Place to put next child relative to origin of the container.
1700 for (const auto &child_wid : this->children) {
1701 uint child_width = child_wid->current_x;
1702 uint child_x = x + (rtl ? position - child_width - child_wid->padding.left : position + child_wid->padding.left);
1703 uint child_y = y + child_wid->padding.top;
1704
1705 child_wid->AssignSizePosition(sizing, child_x, child_y, child_width, child_wid->current_y, rtl);
1706 if (child_wid->current_x != 0) {
1707 uint padded_child_width = child_width + child_wid->padding.Horizontal() + inter;
1708 position = rtl ? position - padded_child_width : position + padded_child_width;
1709 }
1710 }
1711}
1712
1718
1719void NWidgetHorizontalLTR::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool)
1720{
1721 NWidgetHorizontal::AssignSizePosition(sizing, x, y, given_width, given_height, false);
1722}
1723
1728
1730{
1731 this->smallest_x = 0; // Biggest child.
1732 this->smallest_y = 0; // Sum of minimal size of all children.
1733 this->fill_x = 1; // smallest common child fill step.
1734 this->fill_y = 0; // smallest non-zero child widget fill step.
1735 this->resize_x = 1; // smallest common child resize step.
1736 this->resize_y = 0; // smallest non-zero child widget resize step.
1737 this->gaps = 0;
1738
1739 /* 1a. Forward call, collect longest/widest child length. */
1740 uint highest = 0; // Highest child found.
1741 uint max_hor_fill = 0; // Biggest horizontal fill step.
1742 for (const auto &child_wid : this->children) {
1743 child_wid->SetupSmallestSize(w);
1744 highest = std::max(highest, child_wid->smallest_y);
1745 max_hor_fill = std::max(max_hor_fill, child_wid->GetHorizontalStepSize(ST_SMALLEST));
1746 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1747 if (child_wid->smallest_y != 0 || child_wid->fill_y != 0) this->gaps++;
1748 }
1749 if (this->gaps > 0) this->gaps--; // Number of gaps is number of widgets less one.
1750 /* 1b. Make the container wider if needed to accommodate all children nicely. */
1751 [[maybe_unused]] uint max_smallest = this->smallest_x + 3 * max_hor_fill; // Upper limit to computing smallest height.
1752 uint cur_width = this->smallest_x;
1753 for (;;) {
1754 for (const auto &child_wid : this->children) {
1755 uint step_size = child_wid->GetHorizontalStepSize(ST_SMALLEST);
1756 uint child_width = child_wid->smallest_x + child_wid->padding.Horizontal();
1757 if (step_size > 1 && child_width < cur_width) { // Small step sizes or already fitting children are not interesting.
1758 uint remainder = (cur_width - child_width) % step_size;
1759 if (remainder > 0) { // Child did not fit entirely, widen the container.
1760 cur_width += step_size - remainder;
1761 assert(cur_width < max_smallest); // Safeguard against infinite width expansion.
1762 /* Remaining children will adapt to the new cur_width, thus speeding up the computation. */
1763 }
1764 }
1765 }
1766 if (this->smallest_x == cur_width) break;
1767 this->smallest_x = cur_width; // Smallest width got changed, try again.
1768 }
1769 /* 2. For containers that must maintain equal width, extend children minimal size. */
1770 for (const auto &child_wid : this->children) {
1771 child_wid->smallest_x = this->smallest_x - child_wid->padding.Horizontal();
1772 child_wid->ApplyAspectRatio();
1773 highest = std::max(highest, child_wid->smallest_y);
1774 }
1776 for (const auto &child_wid : this->children) {
1777 if (child_wid->fill_y == 1) child_wid->smallest_y = highest;
1778 }
1779 }
1780 /* 3. Compute smallest, fill, and resize values of the container. */
1781 for (const auto &child_wid : this->children) {
1782 this->smallest_y += child_wid->smallest_y + child_wid->padding.Vertical();
1783 if (child_wid->fill_y > 0) {
1784 if (this->fill_y == 0 || this->fill_y > child_wid->fill_y) this->fill_y = child_wid->fill_y;
1785 }
1786 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1787
1788 if (child_wid->resize_y > 0) {
1789 if (this->resize_y == 0 || this->resize_y > child_wid->resize_y) this->resize_y = child_wid->resize_y;
1790 }
1791 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1792 }
1793 if (this->fill_y == 0 && this->pip_ratio_pre + this->pip_ratio_inter + this->pip_ratio_post > 0) this->fill_y = 1;
1794 /* 4. Increase by required PIP space. */
1795 this->smallest_y += this->pip_pre + this->gaps * this->pip_inter + this->pip_post;
1796}
1797
1798void NWidgetVertical::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1799{
1800 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1801
1802 /* Compute additional height given to us. */
1803 uint additional_length = given_height - (this->pip_pre + this->gaps * this->pip_inter + this->pip_post);
1804 for (const auto &child_wid : this->children) {
1805 if (child_wid->smallest_y != 0 || child_wid->fill_y != 0) additional_length -= child_wid->smallest_y + child_wid->padding.Vertical();
1806 }
1807
1808 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1809
1810 /* Like the horizontal container, the vertical container also distributes additional height evenly, starting with the children with the biggest resize steps.
1811 * It also stores computed widths and heights into current_x and current_y values of the child.
1812 */
1813
1814 /* 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. */
1815 int num_changing_childs = 0; // Number of children that can change size.
1816 uint biggest_stepsize = 0;
1817 for (const auto &child_wid : this->children) {
1818 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1819 if (vert_step > 0) {
1820 if (!flags.Test(NWidContainerFlag::BigFirst)) num_changing_childs++;
1821 biggest_stepsize = std::max(biggest_stepsize, vert_step);
1822 } else {
1823 child_wid->current_y = child_wid->smallest_y;
1824 }
1825
1826 uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1827 child_wid->current_x = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1828 }
1829
1830 /* First.5 loop: count how many children are of the biggest step size. */
1831 if (this->flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1832 for (const auto &child_wid : this->children) {
1833 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1834 if (vert_step == biggest_stepsize) {
1835 num_changing_childs++;
1836 }
1837 }
1838 }
1839
1840 /* Second loop: Allocate the additional vertical space over the resizing children, starting with the biggest resize steps. */
1841 while (biggest_stepsize > 0) {
1842 uint next_biggest_stepsize = 0;
1843 for (const auto &child_wid : this->children) {
1844 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1845 if (vert_step > biggest_stepsize) continue; // Already done
1846 if (vert_step == biggest_stepsize) {
1847 uint increment = additional_length / num_changing_childs;
1848 num_changing_childs--;
1849 if (vert_step > 1) increment -= increment % vert_step;
1850 child_wid->current_y = child_wid->smallest_y + increment;
1851 additional_length -= increment;
1852 continue;
1853 }
1854 next_biggest_stepsize = std::max(next_biggest_stepsize, vert_step);
1855 }
1856 biggest_stepsize = next_biggest_stepsize;
1857
1858 if (num_changing_childs == 0 && flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1859 /* Second.5 loop: count how many children are of the updated biggest step size. */
1860 for (const auto &child_wid : this->children) {
1861 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1862 if (vert_step == biggest_stepsize) {
1863 num_changing_childs++;
1864 }
1865 }
1866 }
1867 }
1868 assert(num_changing_childs == 0);
1869
1870 uint pre = this->pip_pre;
1871 uint inter = this->pip_inter;
1872
1873 if (additional_length > 0) {
1874 /* Allocate remaining space by pip ratios. If this doesn't round exactly, the unused space will fall into pip_post
1875 * which is never explicitly needed. */
1876 int r = this->pip_ratio_pre + this->gaps * this->pip_ratio_inter + this->pip_ratio_post;
1877 if (r > 0) {
1878 pre += this->pip_ratio_pre * additional_length / r;
1879 if (this->gaps > 0) inter += this->pip_ratio_inter * additional_length / r;
1880 }
1881 }
1882
1883 /* Third loop: Compute position and call the child. */
1884 uint position = pre; // Place to put next child relative to origin of the container.
1885 for (const auto &child_wid : this->children) {
1886 uint child_x = x + (rtl ? child_wid->padding.right : child_wid->padding.left);
1887 uint child_height = child_wid->current_y;
1888
1889 child_wid->AssignSizePosition(sizing, child_x, y + position + child_wid->padding.top, child_wid->current_x, child_height, rtl);
1890 if (child_wid->current_y != 0) {
1891 position += child_height + child_wid->padding.Vertical() + inter;
1892 }
1893 }
1894}
1895
1902{
1903 this->SetMinimalSize(width, height);
1904 this->SetResize(0, 0);
1905}
1906
1908{
1909 this->smallest_x = this->min_x;
1910 this->smallest_y = this->min_y;
1911 this->ApplyAspectRatio();
1912}
1913
1917
1919{
1920 /* Spacer widget is never normally visible. */
1921
1922 if (_draw_widget_outlines && this->current_x != 0 && this->current_y != 0) {
1923 /* Spacers indicate a potential design issue, so get extra highlighting. */
1924 GfxFillRect(this->GetCurrentRect(), PC_WHITE, FILLRECT_CHECKER);
1925
1926 DrawOutline(w, this);
1927 }
1928}
1929
1931{
1932 /* Spacer widget never need repainting. */
1933}
1934
1936{
1937 return nullptr;
1938}
1939
1940NWidgetMatrix::NWidgetMatrix(Colours colour, WidgetID index) : NWidgetPIPContainer(NWID_MATRIX, NWidContainerFlag::EqualSize), index(index)
1941{
1942 this->colour = colour;
1943}
1944
1950{
1951 this->clicked = clicked;
1952 if (this->clicked >= 0 && this->sb != nullptr && this->widgets_x != 0) {
1953 int vpos = (this->clicked / this->widgets_x) * this->widget_h; // Vertical position of the top.
1954 /* Need to scroll down -> Scroll to the bottom.
1955 * However, last entry has no 'this->pip_inter' underneath, and we must stay below this->sb->GetCount() */
1956 if (this->sb->GetPosition() < vpos) vpos += this->widget_h - this->pip_inter - 1;
1957 this->sb->ScrollTowards(vpos);
1958 }
1959}
1960
1967{
1968 this->count = count;
1969
1970 if (this->sb == nullptr || this->widgets_x == 0) return;
1971
1972 /* We need to get the number of pixels the matrix is high/wide.
1973 * So, determine the number of rows/columns based on the number of
1974 * columns/rows (one is constant/unscrollable).
1975 * Then multiply that by the height of a widget, and add the pre
1976 * and post spacing "offsets". */
1977 count = CeilDiv(count, this->sb->IsVertical() ? this->widgets_x : this->widgets_y);
1978 count *= (this->sb->IsVertical() ? this->children.front()->smallest_y : this->children.front()->smallest_x) + this->pip_inter;
1979 if (count > 0) count -= this->pip_inter; // We counted an inter too much in the multiplication above
1980 count += this->pip_pre + this->pip_post;
1981 this->sb->SetCount(count);
1982 this->sb->SetCapacity(this->sb->IsVertical() ? this->current_y : this->current_x);
1983 this->sb->SetStepSize(this->sb->IsVertical() ? this->widget_h : this->widget_w);
1984}
1985
1991{
1992 this->sb = sb;
1993}
1994
2000{
2001 return this->current_element;
2002}
2003
2005{
2006 assert(this->children.size() == 1);
2007
2008 this->children.front()->SetupSmallestSize(w);
2009
2010 Dimension padding = { (uint)this->pip_pre + this->pip_post, (uint)this->pip_pre + this->pip_post};
2011 Dimension size = {this->children.front()->smallest_x + padding.width, this->children.front()->smallest_y + padding.height};
2012 Dimension fill = {0, 0};
2013 Dimension resize = {this->pip_inter + this->children.front()->smallest_x, this->pip_inter + this->children.front()->smallest_y};
2014
2015 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
2016
2017 this->smallest_x = size.width;
2018 this->smallest_y = size.height;
2019 this->fill_x = fill.width;
2020 this->fill_y = fill.height;
2021 this->resize_x = resize.width;
2022 this->resize_y = resize.height;
2023 this->ApplyAspectRatio();
2024}
2025
2026void NWidgetMatrix::AssignSizePosition(SizingType, int x, int y, uint given_width, uint given_height, bool)
2027{
2028 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
2029
2030 this->pos_x = x;
2031 this->pos_y = y;
2032 this->current_x = given_width;
2033 this->current_y = given_height;
2034
2035 /* Determine the size of the widgets, and the number of visible widgets on each of the axis. */
2036 this->widget_w = this->children.front()->smallest_x + this->pip_inter;
2037 this->widget_h = this->children.front()->smallest_y + this->pip_inter;
2038
2039 /* Account for the pip_inter is between widgets, so we need to account for that when
2040 * the division assumes pip_inter is used for all widgets. */
2041 this->widgets_x = CeilDiv(this->current_x - this->pip_pre - this->pip_post + this->pip_inter, this->widget_w);
2042 this->widgets_y = CeilDiv(this->current_y - this->pip_pre - this->pip_post + this->pip_inter, this->widget_h);
2043
2044 /* When resizing, update the scrollbar's count. E.g. with a vertical
2045 * scrollbar becoming wider or narrower means the amount of rows in
2046 * the scrollbar becomes respectively smaller or higher. */
2047 this->SetCount(this->count);
2048}
2049
2051{
2052 if (this->index >= 0) widget_lookup[this->index] = this;
2054}
2055
2057{
2058 /* Falls outside of the matrix widget. */
2059 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
2060
2061 int start_x, start_y, base_offs_x, base_offs_y;
2062 this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
2063
2064 bool rtl = _current_text_dir == TD_RTL;
2065
2066 int widget_col = (rtl ?
2067 -x + (int)this->pip_post + this->pos_x + base_offs_x + this->widget_w - 1 - (int)this->pip_inter :
2068 x - (int)this->pip_pre - this->pos_x - base_offs_x
2069 ) / this->widget_w;
2070
2071 int widget_row = (y - base_offs_y - (int)this->pip_pre - this->pos_y) / this->widget_h;
2072
2073 this->current_element = (widget_row + start_y) * this->widgets_x + start_x + widget_col;
2074 if (this->current_element >= this->count) return nullptr;
2075
2076 NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->children.front().get());
2077 assert(child != nullptr);
2079 this->pos_x + (rtl ? this->pip_post - widget_col * this->widget_w : this->pip_pre + widget_col * this->widget_w) + base_offs_x,
2080 this->pos_y + this->pip_pre + widget_row * this->widget_h + base_offs_y,
2081 child->smallest_x, child->smallest_y, rtl);
2082
2083 return child->GetWidgetFromPos(x, y);
2084}
2085
2086/* virtual */ void NWidgetMatrix::Draw(const Window *w)
2087{
2088 /* Fill the background. */
2089 GfxFillRect(this->GetCurrentRect(), GetColourGradient(this->colour, SHADE_LIGHT));
2090
2091 /* Set up a clipping area for the previews. */
2092 bool rtl = _current_text_dir == TD_RTL;
2093 DrawPixelInfo tmp_dpi;
2094 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;
2095
2096 {
2097 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
2098
2099 /* Get the appropriate offsets so we can draw the right widgets. */
2100 NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->children.front().get());
2101 assert(child != nullptr);
2102 int start_x, start_y, base_offs_x, base_offs_y;
2103 this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
2104
2105 int offs_y = base_offs_y;
2106 for (int y = start_y; y < start_y + this->widgets_y + 1; y++, offs_y += this->widget_h) {
2107 /* Are we within bounds? */
2108 if (offs_y + child->smallest_y <= 0) continue;
2109 if (offs_y >= (int)this->current_y) break;
2110
2111 /* We've passed our amount of widgets. */
2112 if (y * this->widgets_x >= this->count) break;
2113
2114 int offs_x = base_offs_x;
2115 for (int x = start_x; x < start_x + this->widgets_x + 1; x++, offs_x += rtl ? -this->widget_w : this->widget_w) {
2116 /* Are we within bounds? */
2117 if (offs_x + child->smallest_x <= 0) continue;
2118 if (offs_x >= (int)this->current_x) continue;
2119
2120 /* Do we have this many widgets? */
2121 this->current_element = y * this->widgets_x + x;
2122 if (this->current_element >= this->count) break;
2123
2124 child->AssignSizePosition(ST_RESIZE, offs_x, offs_y, child->smallest_x, child->smallest_y, rtl);
2125 child->SetLowered(this->clicked == this->current_element);
2126 child->Draw(w);
2127 }
2128 }
2129 }
2130
2131 DrawOutline(w, this);
2132}
2133
2141void NWidgetMatrix::GetScrollOffsets(int &start_x, int &start_y, int &base_offs_x, int &base_offs_y)
2142{
2143 base_offs_x = _current_text_dir == TD_RTL ? this->widget_w * (this->widgets_x - 1) : 0;
2144 base_offs_y = 0;
2145 start_x = 0;
2146 start_y = 0;
2147 if (this->sb != nullptr) {
2148 if (this->sb->IsVertical()) {
2149 start_y = this->sb->GetPosition() / this->widget_h;
2150 base_offs_y += -this->sb->GetPosition() + start_y * this->widget_h;
2151 } else {
2152 start_x = this->sb->GetPosition() / this->widget_w;
2153 int sub_x = this->sb->GetPosition() - start_x * this->widget_w;
2154 if (_current_text_dir == TD_RTL) {
2155 base_offs_x += sub_x;
2156 } else {
2157 base_offs_x -= sub_x;
2158 }
2159 }
2160 }
2161}
2162
2172NWidgetBackground::NWidgetBackground(WidgetType tp, Colours colour, WidgetID index, std::unique_ptr<NWidgetPIPContainer> &&child) : NWidgetCore(tp, colour, index, 1, 1, {}, STR_NULL)
2173{
2174 assert(tp == WWT_PANEL || tp == WWT_INSET || tp == WWT_FRAME);
2175 this->child = std::move(child);
2176 if (this->child != nullptr) this->child->parent = this;
2177 this->SetAlignment(SA_TOP | SA_LEFT);
2178}
2179
2187void NWidgetBackground::Add(std::unique_ptr<NWidgetBase> &&nwid)
2188{
2189 if (this->child == nullptr) {
2190 this->child = std::make_unique<NWidgetVertical>();
2191 }
2192 nwid->parent = this->child.get();
2193 this->child->Add(std::move(nwid));
2194}
2195
2206void NWidgetBackground::SetPIP(uint8_t pip_pre, uint8_t pip_inter, uint8_t pip_post)
2207{
2208 if (this->child == nullptr) {
2209 this->child = std::make_unique<NWidgetVertical>();
2210 }
2211 this->child->parent = this;
2212 this->child->SetPIP(pip_pre, pip_inter, pip_post);
2213}
2214
2225void NWidgetBackground::SetPIPRatio(uint8_t pip_ratio_pre, uint8_t pip_ratio_inter, uint8_t pip_ratio_post)
2226{
2227 if (this->child == nullptr) {
2228 this->child = std::make_unique<NWidgetVertical>();
2229 }
2230 this->child->parent = this;
2231 this->child->SetPIPRatio(pip_ratio_pre, pip_ratio_inter, pip_ratio_post);
2232}
2233
2234void NWidgetBackground::AdjustPaddingForZoom()
2235{
2236 if (child != nullptr) child->AdjustPaddingForZoom();
2237 NWidgetCore::AdjustPaddingForZoom();
2238}
2239
2241{
2242 if (this->child != nullptr) {
2243 this->child->SetupSmallestSize(w);
2244
2245 this->smallest_x = this->child->smallest_x;
2246 this->smallest_y = this->child->smallest_y;
2247 this->fill_x = this->child->fill_x;
2248 this->fill_y = this->child->fill_y;
2249 this->resize_x = this->child->resize_x;
2250 this->resize_y = this->child->resize_y;
2251
2252 /* Don't apply automatic padding if there is no child widget. */
2253 if (w == nullptr) return;
2254
2255 if (this->type == WWT_FRAME) {
2256 /* Account for the size of the frame's text if that exists */
2257 this->child->padding = WidgetDimensions::scaled.frametext;
2258 this->child->padding.top = std::max<uint8_t>(WidgetDimensions::scaled.frametext.top, this->GetString() != STR_NULL ? GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.frametext.top / 2 : 0);
2259
2260 this->smallest_x += this->child->padding.Horizontal();
2261 this->smallest_y += this->child->padding.Vertical();
2262
2263 std::string text = GetStringForWidget(w, this);
2264 this->smallest_x = std::max(this->smallest_x, GetStringBoundingBox(text, this->text_size).width + WidgetDimensions::scaled.frametext.Horizontal());
2265 } else if (this->type == WWT_INSET) {
2266 /* Apply automatic padding for bevel thickness. */
2267 this->child->padding = WidgetDimensions::scaled.bevel;
2268
2269 this->smallest_x += this->child->padding.Horizontal();
2270 this->smallest_y += this->child->padding.Vertical();
2271 }
2272 this->ApplyAspectRatio();
2273 } else {
2274 Dimension d = {this->min_x, this->min_y};
2275 Dimension fill = {this->fill_x, this->fill_y};
2276 Dimension resize = {this->resize_x, this->resize_y};
2277 if (w != nullptr) { // A non-nullptr window pointer acts as switch to turn dynamic widget size on.
2278 if (this->type == WWT_FRAME || this->type == WWT_INSET) {
2279 std::string text = GetStringForWidget(w, this);
2280 Dimension background = GetStringBoundingBox(text, this->text_size);
2281 background.width += (this->type == WWT_FRAME) ? (WidgetDimensions::scaled.frametext.Horizontal()) : (WidgetDimensions::scaled.inset.Horizontal());
2282 d = maxdim(d, background);
2283 }
2284 if (this->index >= 0) {
2286 switch (this->type) {
2287 default: NOT_REACHED();
2291 }
2292 w->UpdateWidgetSize(this->index, d, padding, fill, resize);
2293 }
2294 }
2295 this->smallest_x = d.width;
2296 this->smallest_y = d.height;
2297 this->fill_x = fill.width;
2298 this->fill_y = fill.height;
2299 this->resize_x = resize.width;
2300 this->resize_y = resize.height;
2301 this->ApplyAspectRatio();
2302 }
2303}
2304
2305void NWidgetBackground::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
2306{
2307 this->StoreSizePosition(sizing, x, y, given_width, given_height);
2308
2309 if (this->child != nullptr) {
2310 uint x_offset = (rtl ? this->child->padding.right : this->child->padding.left);
2311 uint width = given_width - this->child->padding.Horizontal();
2312 uint height = given_height - this->child->padding.Vertical();
2313 this->child->AssignSizePosition(sizing, x + x_offset, y + this->child->padding.top, width, height, rtl);
2314 }
2315}
2316
2318{
2319 if (this->index >= 0) widget_lookup[this->index] = this;
2320 if (this->child != nullptr) this->child->FillWidgetLookup(widget_lookup);
2321}
2322
2324{
2325 if (this->current_x == 0 || this->current_y == 0) return;
2326
2327 Rect r = this->GetCurrentRect();
2328
2329 const DrawPixelInfo *dpi = _cur_dpi;
2330 if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2331
2332 switch (this->type) {
2333 case WWT_PANEL:
2334 DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, this->IsLowered() ? FrameFlag::Lowered : FrameFlags{});
2335 break;
2336
2337 case WWT_FRAME:
2338 DrawFrame(r, this->colour, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
2339 break;
2340
2341 case WWT_INSET:
2342 DrawInset(r, this->colour, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
2343 break;
2344
2345 default:
2346 NOT_REACHED();
2347 }
2348
2349 if (this->index >= 0) w->DrawWidget(r, this->index);
2350 if (this->child != nullptr) this->child->Draw(w);
2351
2352 if (this->IsDisabled()) {
2354 }
2355
2356 DrawOutline(w, this);
2357}
2358
2360{
2361 NWidgetCore *nwid = nullptr;
2362 if (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) {
2363 if (this->child != nullptr) nwid = this->child->GetWidgetFromPos(x, y);
2364 if (nwid == nullptr) nwid = this;
2365 }
2366 return nwid;
2367}
2368
2370{
2371 NWidgetBase *nwid = nullptr;
2372 if (this->child != nullptr) nwid = this->child->GetWidgetOfType(tp);
2373 if (nwid == nullptr && this->type == tp) nwid = this;
2374 return nwid;
2375}
2376
2377NWidgetViewport::NWidgetViewport(WidgetID index) : NWidgetCore(NWID_VIEWPORT, INVALID_COLOUR, index, 1, 1, {}, STR_NULL)
2378{
2379}
2380
2382{
2383 this->smallest_x = this->min_x;
2384 this->smallest_y = this->min_y;
2385 this->ApplyAspectRatio();
2386}
2387
2389{
2390 if (this->current_x == 0 || this->current_y == 0) return;
2391
2394 _transparency_opt &= (1 << TO_SIGNS) | (1 << TO_TEXT); // Disable all transparency, except textual stuff
2395 w->DrawViewport();
2396 _transparency_opt = to_backup;
2397 } else {
2398 w->DrawViewport();
2399 }
2400
2401 /* Optionally shade the viewport. */
2402 if (this->disp_flags.Any({NWidgetDisplayFlag::ShadeGrey, NWidgetDisplayFlag::ShadeDimmed})) {
2404 }
2405
2406 DrawOutline(w, this);
2407}
2408
2415void NWidgetViewport::InitializeViewport(Window *w, std::variant<TileIndex, VehicleID> focus, ZoomLevel zoom)
2416{
2417 InitializeWindowViewport(w, this->pos_x, this->pos_y, this->current_x, this->current_y, focus, zoom);
2418}
2419
2425{
2426 Viewport *vp = w->viewport;
2427 if (vp != nullptr) {
2428 vp->left = w->left + this->pos_x;
2429 vp->top = w->top + this->pos_y;
2430 vp->width = this->current_x;
2431 vp->height = this->current_y;
2432
2433 vp->virtual_width = ScaleByZoom(vp->width, vp->zoom);
2434 vp->virtual_height = ScaleByZoom(vp->height, vp->zoom);
2435 }
2436}
2437
2447Scrollbar::size_type Scrollbar::GetScrolledRowFromWidget(int clickpos, const Window * const w, WidgetID widget, int padding, int line_height) const
2448{
2449 int pos = w->GetRowFromWidget(clickpos, widget, padding, line_height);
2450 if (pos != INT_MAX) pos += this->GetPosition();
2451 return (pos < 0 || pos >= this->GetCount()) ? Scrollbar::npos : pos;
2452}
2453
2468EventState Scrollbar::UpdateListPositionOnKeyPress(int &list_position, uint16_t keycode) const
2469{
2470 int new_pos = list_position;
2471 switch (keycode) {
2472 case WKC_UP:
2473 /* scroll up by one */
2474 new_pos--;
2475 break;
2476
2477 case WKC_DOWN:
2478 /* scroll down by one */
2479 new_pos++;
2480 break;
2481
2482 case WKC_PAGEUP:
2483 /* scroll up a page */
2484 new_pos -= this->GetCapacity();
2485 break;
2486
2487 case WKC_PAGEDOWN:
2488 /* scroll down a page */
2489 new_pos += this->GetCapacity();
2490 break;
2491
2492 case WKC_HOME:
2493 /* jump to beginning */
2494 new_pos = 0;
2495 break;
2496
2497 case WKC_END:
2498 /* jump to end */
2499 new_pos = this->GetCount() - 1;
2500 break;
2501
2502 default:
2503 return ES_NOT_HANDLED;
2504 }
2505
2506 /* If there are no elements, there is nothing to scroll/update. */
2507 if (this->GetCount() != 0) {
2508 list_position = Clamp(new_pos, 0, this->GetCount() - 1);
2509 }
2510 return ES_HANDLED;
2511}
2512
2513
2522{
2523 NWidgetBase *nwid = w->GetWidget<NWidgetBase>(widget);
2524 if (this->IsVertical()) {
2525 this->SetCapacity(((int)nwid->current_y - padding) / (int)nwid->resize_y);
2526 } else {
2527 this->SetCapacity(((int)nwid->current_x - padding) / (int)nwid->resize_x);
2528 }
2529}
2530
2538Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
2539{
2540 const int count = sb.GetCount() * resize_step;
2541 const int position = sb.GetPosition() * resize_step;
2542
2543 if (sb.IsVertical()) {
2544 r.top -= position;
2545 r.bottom = r.top + count;
2546 } else {
2547 bool rtl = _current_text_dir == TD_RTL;
2548 if (rtl) {
2549 r.right += position;
2550 r.left = r.right - count;
2551 } else {
2552 r.left -= position;
2553 r.right = r.left + count;
2554 }
2555 }
2556
2557 return r;
2558}
2559
2566NWidgetScrollbar::NWidgetScrollbar(WidgetType tp, Colours colour, WidgetID index) : NWidgetCore(tp, colour, index, 1, 1, {}, STR_NULL), Scrollbar(tp != NWID_HSCROLLBAR)
2567{
2568 assert(tp == NWID_HSCROLLBAR || tp == NWID_VSCROLLBAR);
2569
2570 switch (this->type) {
2571 case NWID_HSCROLLBAR:
2572 this->SetResize(1, 0);
2573 this->SetFill(1, 0);
2574 this->SetToolTip(STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST);
2575 break;
2576
2577 case NWID_VSCROLLBAR:
2578 this->SetResize(0, 1);
2579 this->SetFill(0, 1);
2580 this->SetToolTip(STR_TOOLTIP_VSCROLL_BAR_SCROLLS_LIST);
2581 break;
2582
2583 default: NOT_REACHED();
2584 }
2585}
2586
2588{
2589 this->min_x = 0;
2590 this->min_y = 0;
2591
2592 switch (this->type) {
2593 case NWID_HSCROLLBAR:
2594 this->SetMinimalSizeAbsolute(NWidgetScrollbar::GetHorizontalDimension().width * 3, NWidgetScrollbar::GetHorizontalDimension().height);
2595 break;
2596
2597 case NWID_VSCROLLBAR:
2598 this->SetMinimalSizeAbsolute(NWidgetScrollbar::GetVerticalDimension().width, NWidgetScrollbar::GetVerticalDimension().height * 3);
2599 break;
2600
2601 default: NOT_REACHED();
2602 }
2603
2604 this->smallest_x = this->min_x;
2605 this->smallest_y = this->min_y;
2606}
2607
2609{
2610 if (this->current_x == 0 || this->current_y == 0) return;
2611
2612 Rect r = this->GetCurrentRect();
2613
2614 const DrawPixelInfo *dpi = _cur_dpi;
2615 if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2616
2617 bool up_lowered = this->disp_flags.Test(NWidgetDisplayFlag::ScrollbarUp);
2618 bool down_lowered = this->disp_flags.Test(NWidgetDisplayFlag::ScrollbarDown);
2619 bool middle_lowered = !this->disp_flags.Any({NWidgetDisplayFlag::ScrollbarUp, NWidgetDisplayFlag::ScrollbarDown}) && w->mouse_capture_widget == this->index;
2620
2621 if (this->type == NWID_HSCROLLBAR) {
2622 DrawHorizontalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2623 } else {
2624 DrawVerticalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2625 }
2626
2627 if (this->IsDisabled()) {
2629 }
2630
2631 DrawOutline(w, this);
2632}
2633
2634/* static */ void NWidgetScrollbar::InvalidateDimensionCache()
2635{
2636 vertical_dimension.width = vertical_dimension.height = 0;
2637 horizontal_dimension.width = horizontal_dimension.height = 0;
2638}
2639
2640/* static */ Dimension NWidgetScrollbar::GetVerticalDimension()
2641{
2642 if (vertical_dimension.width == 0) {
2643 vertical_dimension = maxdim(GetScaledSpriteSize(SPR_ARROW_UP), GetScaledSpriteSize(SPR_ARROW_DOWN));
2646 }
2647 return vertical_dimension;
2648}
2649
2650/* static */ Dimension NWidgetScrollbar::GetHorizontalDimension()
2651{
2652 if (horizontal_dimension.width == 0) {
2653 horizontal_dimension = maxdim(GetScaledSpriteSize(SPR_ARROW_LEFT), GetScaledSpriteSize(SPR_ARROW_RIGHT));
2656 }
2657 return horizontal_dimension;
2658}
2659
2662
2665{
2666 shadebox_dimension.width = shadebox_dimension.height = 0;
2667 debugbox_dimension.width = debugbox_dimension.height = 0;
2668 defsizebox_dimension.width = defsizebox_dimension.height = 0;
2669 stickybox_dimension.width = stickybox_dimension.height = 0;
2670 resizebox_dimension.width = resizebox_dimension.height = 0;
2671 closebox_dimension.width = closebox_dimension.height = 0;
2672 dropdown_dimension.width = dropdown_dimension.height = 0;
2673}
2674
2682
2691NWidgetLeaf::NWidgetLeaf(WidgetType tp, Colours colour, WidgetID index, const WidgetData &data, StringID tip) : NWidgetCore(tp, colour, index, 1, 1, data, tip)
2692{
2693 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);
2694 this->min_x = 0;
2695 this->min_y = 0;
2696 this->SetResize(0, 0);
2697
2698 switch (tp) {
2699 case WWT_EMPTY:
2700 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_EMPTY should not have a colour");
2701 break;
2702
2703 case WWT_TEXT:
2704 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_TEXT should not have a colour");
2705 this->SetFill(0, 0);
2707 break;
2708
2709 case WWT_LABEL:
2710 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_LABEL should not have a colour");
2711 [[fallthrough]];
2712
2713 case WWT_PUSHBTN:
2714 case WWT_IMGBTN:
2715 case WWT_PUSHIMGBTN:
2716 case WWT_IMGBTN_2:
2717 case WWT_TEXTBTN:
2718 case WWT_PUSHTXTBTN:
2719 case WWT_TEXTBTN_2:
2720 case WWT_MATRIX:
2722 case NWID_PUSHBUTTON_DROPDOWN:
2723 this->SetFill(0, 0);
2724 break;
2725
2726 case WWT_ARROWBTN:
2727 case WWT_PUSHARROWBTN:
2728 this->SetFill(0, 0);
2729 this->SetAspect(WidgetDimensions::ASPECT_LEFT_RIGHT_BUTTON);
2730 break;
2731
2732 case WWT_EDITBOX:
2733 this->SetFill(0, 0);
2734 break;
2735
2736 case WWT_CAPTION:
2737 this->SetFill(1, 0);
2738 this->SetResize(1, 0);
2740 this->SetMinimalTextLines(1, WidgetDimensions::unscaled.captiontext.Vertical(), FS_NORMAL);
2741 this->SetToolTip(STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
2742 break;
2743
2744 case WWT_STICKYBOX:
2745 this->SetFill(0, 0);
2747 this->SetToolTip(STR_TOOLTIP_STICKY);
2748 this->SetAspect(this->min_x, this->min_y);
2749 break;
2750
2751 case WWT_SHADEBOX:
2752 this->SetFill(0, 0);
2754 this->SetToolTip(STR_TOOLTIP_SHADE);
2755 this->SetAspect(this->min_x, this->min_y);
2756 break;
2757
2758 case WWT_DEBUGBOX:
2759 this->SetFill(0, 0);
2761 this->SetToolTip(STR_TOOLTIP_DEBUG);
2762 this->SetAspect(this->min_x, this->min_y);
2763 break;
2764
2765 case WWT_DEFSIZEBOX:
2766 this->SetFill(0, 0);
2768 this->SetToolTip(STR_TOOLTIP_DEFSIZE);
2769 this->SetAspect(this->min_x, this->min_y);
2770 break;
2771
2772 case WWT_RESIZEBOX:
2773 this->SetFill(0, 0);
2776 this->SetToolTip(STR_TOOLTIP_RESIZE);
2777 break;
2778
2779 case WWT_CLOSEBOX:
2780 this->SetFill(0, 0);
2782 this->SetToolTip(STR_TOOLTIP_CLOSE_WINDOW);
2783 this->SetAspect(this->min_x, this->min_y);
2784 break;
2785
2786 case WWT_DROPDOWN:
2787 this->SetFill(0, 0);
2789 this->SetAlignment(SA_TOP | SA_LEFT);
2790 break;
2791
2792 default:
2793 NOT_REACHED();
2794 }
2795}
2796
2798{
2799 Dimension padding = {0, 0};
2800 Dimension size = {this->min_x, this->min_y};
2801 Dimension fill = {this->fill_x, this->fill_y};
2802 Dimension resize = {this->resize_x, this->resize_y};
2803 switch (this->type) {
2804 case WWT_EMPTY: {
2805 break;
2806 }
2807 case WWT_MATRIX: {
2809 break;
2810 }
2811 case WWT_SHADEBOX: {
2813 if (NWidgetLeaf::shadebox_dimension.width == 0) {
2814 NWidgetLeaf::shadebox_dimension = maxdim(GetScaledSpriteSize(SPR_WINDOW_SHADE), GetScaledSpriteSize(SPR_WINDOW_UNSHADE));
2817 }
2819 break;
2820 }
2821 case WWT_DEBUGBOX:
2824 if (NWidgetLeaf::debugbox_dimension.width == 0) {
2828 }
2830 } else {
2831 /* If the setting is disabled we don't want to see it! */
2832 size.width = 0;
2833 fill.width = 0;
2834 resize.width = 0;
2835 }
2836 break;
2837
2838 case WWT_STICKYBOX: {
2840 if (NWidgetLeaf::stickybox_dimension.width == 0) {
2844 }
2846 break;
2847 }
2848
2849 case WWT_DEFSIZEBOX: {
2851 if (NWidgetLeaf::defsizebox_dimension.width == 0) {
2855 }
2857 break;
2858 }
2859
2860 case WWT_RESIZEBOX: {
2862 if (NWidgetLeaf::resizebox_dimension.width == 0) {
2863 NWidgetLeaf::resizebox_dimension = maxdim(GetScaledSpriteSize(SPR_WINDOW_RESIZE_LEFT), GetScaledSpriteSize(SPR_WINDOW_RESIZE_RIGHT));
2866 }
2868 break;
2869 }
2870 case WWT_EDITBOX: {
2871 Dimension sprite_size = GetScaledSpriteSize(_current_text_dir == TD_RTL ? SPR_IMG_DELETE_RIGHT : SPR_IMG_DELETE_LEFT);
2872 size.width = std::max(size.width, ScaleGUITrad(30) + sprite_size.width);
2873 size.height = std::max(sprite_size.height, GetStringBoundingBox("_").height + WidgetDimensions::scaled.framerect.Vertical());
2874 }
2875 [[fallthrough]];
2876 case WWT_PUSHBTN: {
2878 break;
2879 }
2880 case WWT_IMGBTN:
2881 case WWT_IMGBTN_2:
2882 case WWT_PUSHIMGBTN: {
2884 Dimension d2 = GetScaledSpriteSize(this->widget_data.sprite);
2885 if (this->type == WWT_IMGBTN_2) d2 = maxdim(d2, GetScaledSpriteSize(this->widget_data.sprite + 1));
2886 d2.width += padding.width;
2887 d2.height += padding.height;
2888 size = maxdim(size, d2);
2889 break;
2890 }
2891 case WWT_ARROWBTN:
2892 case WWT_PUSHARROWBTN: {
2894 Dimension d2 = maxdim(GetScaledSpriteSize(SPR_ARROW_LEFT), GetScaledSpriteSize(SPR_ARROW_RIGHT));
2895 d2.width += padding.width;
2896 d2.height += padding.height;
2897 size = maxdim(size, d2);
2898 break;
2899 }
2900
2901 case WWT_CLOSEBOX: {
2903 if (NWidgetLeaf::closebox_dimension.width == 0) {
2907 }
2909 break;
2910 }
2911 case WWT_TEXTBTN:
2912 case WWT_PUSHTXTBTN:
2913 case WWT_TEXTBTN_2: {
2915 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2916 d2.width += padding.width;
2917 d2.height += padding.height;
2918 size = maxdim(size, d2);
2919 break;
2920 }
2921 case WWT_LABEL:
2922 case WWT_TEXT: {
2923 size = maxdim(size, GetStringBoundingBox(GetStringForWidget(w, this), this->text_size));
2924 break;
2925 }
2926 case WWT_CAPTION: {
2928 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2929 d2.width += padding.width;
2930 d2.height += padding.height;
2931 size = maxdim(size, d2);
2932 break;
2933 }
2934 case WWT_DROPDOWN:
2936 case NWID_PUSHBUTTON_DROPDOWN: {
2937 if (NWidgetLeaf::dropdown_dimension.width == 0) {
2941 }
2943 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2944 d2.width += padding.width;
2945 d2.height = std::max(d2.height + padding.height, NWidgetLeaf::dropdown_dimension.height);
2946 size = maxdim(size, d2);
2947 break;
2948 }
2949 default:
2950 NOT_REACHED();
2951 }
2952
2953 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
2954
2955 this->smallest_x = size.width;
2956 this->smallest_y = size.height;
2957 this->fill_x = fill.width;
2958 this->fill_y = fill.height;
2959 this->resize_x = resize.width;
2960 this->resize_y = resize.height;
2961 this->ApplyAspectRatio();
2962}
2963
2965{
2966 if (this->current_x == 0 || this->current_y == 0) return;
2967
2968 /* Setup a clipping rectangle... for WWT_EMPTY or WWT_TEXT, an extra scaled pixel is allowed in case text shadow encroaches. */
2969 int extra = (this->type == WWT_EMPTY || this->type == WWT_TEXT) ? ScaleGUITrad(1) : 0;
2970 DrawPixelInfo new_dpi;
2971 if (!FillDrawPixelInfo(&new_dpi, this->pos_x, this->pos_y, this->current_x + extra, this->current_y + extra)) return;
2972 /* ...but keep coordinates relative to the window. */
2973 new_dpi.left += this->pos_x;
2974 new_dpi.top += this->pos_y;
2975
2976 AutoRestoreBackup dpi_backup(_cur_dpi, &new_dpi);
2977
2978 Rect r = this->GetCurrentRect();
2979
2980 bool clicked = this->IsLowered();
2981 switch (this->type) {
2982 case WWT_EMPTY:
2983 /* WWT_EMPTY used as a spacer indicates a potential design issue. */
2984 if (this->index == -1 && _draw_widget_outlines) {
2986 }
2987 break;
2988
2989 case WWT_PUSHBTN:
2990 DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
2991 break;
2992
2993 case WWT_IMGBTN:
2994 case WWT_PUSHIMGBTN:
2995 case WWT_IMGBTN_2:
2996 DrawImageButtons(r, this->type, this->colour, clicked, this->widget_data.sprite, this->align);
2997 break;
2998
2999 case WWT_TEXTBTN:
3000 case WWT_PUSHTXTBTN:
3001 case WWT_TEXTBTN_2:
3002 DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
3003 DrawLabel(r, this->text_colour, GetStringForWidget(w, this, (type & WWT_MASK) == WWT_TEXTBTN_2 && clicked), this->align, this->text_size);
3004 break;
3005
3006 case WWT_ARROWBTN:
3007 case WWT_PUSHARROWBTN: {
3008 SpriteID sprite;
3009 switch (this->widget_data.arrow_widget_type) {
3010 case AWV_DECREASE: sprite = _current_text_dir != TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
3011 case AWV_INCREASE: sprite = _current_text_dir == TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
3012 case AWV_LEFT: sprite = SPR_ARROW_LEFT; break;
3013 case AWV_RIGHT: sprite = SPR_ARROW_RIGHT; break;
3014 default: NOT_REACHED();
3015 }
3016 DrawImageButtons(r, WWT_PUSHIMGBTN, this->colour, clicked, sprite, this->align);
3017 break;
3018 }
3019
3020 case WWT_LABEL:
3021 DrawLabel(r, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3022 break;
3023
3024 case WWT_TEXT:
3025 DrawText(r, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3026 break;
3027
3028 case WWT_MATRIX:
3029 DrawMatrix(r, this->colour, clicked, this->widget_data.matrix.width, this->widget_data.matrix.height, this->resize_x, this->resize_y);
3030 break;
3031
3032 case WWT_EDITBOX: {
3033 const QueryString *query = w->GetQueryString(this->index);
3034 if (query != nullptr) query->DrawEditBox(w, this->index);
3035 break;
3036 }
3037
3038 case WWT_CAPTION:
3039 DrawCaption(r, this->colour, w->owner, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3040 break;
3041
3042 case WWT_SHADEBOX:
3043 DrawShadeBox(r, this->colour, w->IsShaded());
3044 break;
3045
3046 case WWT_DEBUGBOX:
3047 DrawDebugBox(r, this->colour, clicked);
3048 break;
3049
3050 case WWT_STICKYBOX:
3052 break;
3053
3054 case WWT_DEFSIZEBOX:
3055 DrawDefSizeBox(r, this->colour, clicked);
3056 break;
3057
3058 case WWT_RESIZEBOX:
3059 DrawResizeBox(r, this->colour, this->pos_x < (w->width / 2), w->flags.Test(WindowFlag::SizingLeft) || w->flags.Test(WindowFlag::SizingRight), this->widget_data.resize_widget_type == RWV_SHOW_BEVEL);
3060 break;
3061
3062 case WWT_CLOSEBOX:
3063 DrawCloseBox(r, this->colour);
3064 break;
3065
3066 case WWT_DROPDOWN:
3067 DrawButtonDropdown(r, this->colour, false, clicked, GetStringForWidget(w, this), this->align);
3068 break;
3069
3071 case NWID_PUSHBUTTON_DROPDOWN:
3072 DrawButtonDropdown(r, this->colour, clicked, this->disp_flags.Test(NWidgetDisplayFlag::DropdownActive), GetStringForWidget(w, this), this->align);
3073 break;
3074
3075 default:
3076 NOT_REACHED();
3077 }
3078 if (this->index >= 0) w->DrawWidget(r, this->index);
3079
3080 if (this->IsDisabled()) {
3082 }
3083
3084 DrawOutline(w, this);
3085}
3086
3095{
3096 if (_current_text_dir == TD_LTR) {
3097 int button_width = this->pos_x + this->current_x - NWidgetLeaf::dropdown_dimension.width;
3098 return pt.x < button_width;
3099 } else {
3100 int button_left = this->pos_x + NWidgetLeaf::dropdown_dimension.width;
3101 return pt.x >= button_left;
3102 }
3103}
3104
3105/* == Conversion code from NWidgetPart array to NWidgetBase* tree == */
3106
3113{
3114 return tp > WPT_ATTRIBUTE_BEGIN && tp < WPT_ATTRIBUTE_END;
3115}
3116
3124{
3125 switch (nwid.type) {
3126 case WPT_RESIZE: {
3127 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3128 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_RESIZE requires NWidgetResizeBase");
3129 assert(nwid.u.xy.x >= 0 && nwid.u.xy.y >= 0);
3130 nwrb->SetResize(nwid.u.xy.x, nwid.u.xy.y);
3131 break;
3132 }
3133
3134 case WPT_MINSIZE: {
3135 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3136 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_MINSIZE requires NWidgetResizeBase");
3137 assert(nwid.u.xy.x >= 0 && nwid.u.xy.y >= 0);
3138 nwrb->SetMinimalSize(nwid.u.xy.x, nwid.u.xy.y);
3139 break;
3140 }
3141
3142 case WPT_MINTEXTLINES: {
3143 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3144 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_MINTEXTLINES requires NWidgetResizeBase");
3145 assert(nwid.u.text_lines.size >= FS_BEGIN && nwid.u.text_lines.size < FS_END);
3147 break;
3148 }
3149
3150 case WPT_TEXTSTYLE: {
3151 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3152 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_TEXTSTYLE requires NWidgetCore");
3153 nwc->SetTextStyle(nwid.u.text_style.colour, nwid.u.text_style.size);
3154 break;
3155 }
3156
3157 case WPT_ALIGNMENT: {
3158 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3159 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_ALIGNMENT requires NWidgetCore");
3160 nwc->SetAlignment(nwid.u.align.align);
3161 break;
3162 }
3163
3164 case WPT_FILL: {
3165 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3166 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_FILL requires NWidgetResizeBase");
3167 nwrb->SetFill(nwid.u.xy.x, nwid.u.xy.y);
3168 break;
3169 }
3170
3171 case WPT_DATATIP: {
3172 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3173 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_DATATIP requires NWidgetCore");
3174 nwc->widget_data = nwid.u.data_tip.data;
3175 nwc->SetToolTip(nwid.u.data_tip.tooltip);
3176 break;
3177 }
3178
3179 case WPT_PADDING:
3180 if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_PADDING requires NWidgetBase");
3181 dest->SetPadding(nwid.u.padding);
3182 break;
3183
3184 case WPT_PIPSPACE: {
3185 NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(dest);
3186 if (nwc != nullptr) nwc->SetPIP(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3187
3188 NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(dest);
3189 if (nwb != nullptr) nwb->SetPIP(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3190
3191 if (nwc == nullptr && nwb == nullptr) [[unlikely]] throw std::runtime_error("WPT_PIPSPACE requires NWidgetPIPContainer or NWidgetBackground");
3192 break;
3193 }
3194
3195 case WPT_PIPRATIO: {
3196 NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(dest);
3197 if (nwc != nullptr) nwc->SetPIPRatio(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3198
3199 NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(dest);
3200 if (nwb != nullptr) nwb->SetPIPRatio(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3201
3202 if (nwc == nullptr && nwb == nullptr) [[unlikely]] throw std::runtime_error("WPT_PIPRATIO requires NWidgetPIPContainer or NWidgetBackground");
3203 break;
3204 }
3205
3206 case WPT_SCROLLBAR: {
3207 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3208 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_SCROLLBAR requires NWidgetCore");
3209 nwc->scrollbar_index = nwid.u.widget.index;
3210 break;
3211 }
3212
3213 case WPT_ASPECT: {
3214 if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_ASPECT requires NWidgetBase");
3215 dest->aspect_ratio = nwid.u.aspect.ratio;
3216 dest->aspect_flags = nwid.u.aspect.flags;
3217 break;
3218 }
3219
3220 default:
3221 NOT_REACHED();
3222 }
3223}
3224
3231static std::unique_ptr<NWidgetBase> MakeNWidget(const NWidgetPart &nwid)
3232{
3233 assert(!IsAttributeWidgetPartType(nwid.type));
3234 assert(nwid.type != WPT_ENDCONTAINER);
3235
3236 switch (nwid.type) {
3237 case NWID_SPACER: return std::make_unique<NWidgetSpacer>(0, 0);
3238
3239 case WWT_PANEL: [[fallthrough]];
3240 case WWT_INSET: [[fallthrough]];
3241 case WWT_FRAME: return std::make_unique<NWidgetBackground>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index);
3242
3243 case NWID_HORIZONTAL: return std::make_unique<NWidgetHorizontal>(nwid.u.cont_flags);
3244 case NWID_HORIZONTAL_LTR: return std::make_unique<NWidgetHorizontalLTR>(nwid.u.cont_flags);
3245 case NWID_VERTICAL: return std::make_unique<NWidgetVertical>(nwid.u.cont_flags);
3246 case NWID_SELECTION: return std::make_unique<NWidgetStacked>(nwid.u.widget.index);
3247 case NWID_MATRIX: return std::make_unique<NWidgetMatrix>(nwid.u.widget.colour, nwid.u.widget.index);
3248 case NWID_VIEWPORT: return std::make_unique<NWidgetViewport>(nwid.u.widget.index);
3249 case NWID_LAYER: return std::make_unique<NWidgetLayer>(nwid.u.widget.index);
3250
3251 case NWID_HSCROLLBAR: [[fallthrough]];
3252 case NWID_VSCROLLBAR: return std::make_unique<NWidgetScrollbar>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index);
3253
3254 case WPT_FUNCTION: return nwid.u.func_ptr();
3255
3256 default:
3257 assert((nwid.type & WWT_MASK) < WWT_LAST || (nwid.type & WWT_MASK) == NWID_BUTTON_DROPDOWN);
3258 return std::make_unique<NWidgetLeaf>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index, WidgetData{}, STR_NULL);
3259 }
3260}
3261
3275static std::span<const NWidgetPart>::iterator MakeNWidget(std::span<const NWidgetPart>::iterator nwid_begin, std::span<const NWidgetPart>::iterator nwid_end, std::unique_ptr<NWidgetBase> &dest, bool &fill_dest)
3276{
3277 dest = nullptr;
3278
3279 if (IsAttributeWidgetPartType(nwid_begin->type)) [[unlikely]] throw std::runtime_error("Expected non-attribute NWidgetPart type");
3280 if (nwid_begin->type == WPT_ENDCONTAINER) return nwid_begin;
3281
3282 fill_dest = IsContainerWidgetType(nwid_begin->type);
3283 dest = MakeNWidget(*nwid_begin);
3284 if (dest == nullptr) return nwid_begin;
3285
3286 ++nwid_begin;
3287
3288 /* Once a widget is created, we're now looking for attributes. */
3289 while (nwid_begin != nwid_end && IsAttributeWidgetPartType(nwid_begin->type)) {
3290 ApplyNWidgetPartAttribute(*nwid_begin, dest.get());
3291 ++nwid_begin;
3292 }
3293
3294 return nwid_begin;
3295}
3296
3303{
3304 return tp == NWID_HORIZONTAL || tp == NWID_HORIZONTAL_LTR || tp == NWID_VERTICAL || tp == NWID_MATRIX
3305 || tp == WWT_PANEL || tp == WWT_FRAME || tp == WWT_INSET || tp == NWID_SELECTION || tp == NWID_LAYER;
3306}
3307
3315static std::span<const NWidgetPart>::iterator MakeWidgetTree(std::span<const NWidgetPart>::iterator nwid_begin, std::span<const NWidgetPart>::iterator nwid_end, std::unique_ptr<NWidgetBase> &parent)
3316{
3317 /* If *parent == nullptr, only the first widget is read and returned. Otherwise, *parent must point to either
3318 * a #NWidgetContainer or a #NWidgetBackground object, and parts are added as much as possible. */
3319 NWidgetContainer *nwid_cont = dynamic_cast<NWidgetContainer *>(parent.get());
3320 NWidgetBackground *nwid_parent = dynamic_cast<NWidgetBackground *>(parent.get());
3321 assert(parent == nullptr || (nwid_cont != nullptr && nwid_parent == nullptr) || (nwid_cont == nullptr && nwid_parent != nullptr));
3322
3323 while (nwid_begin != nwid_end) {
3324 std::unique_ptr<NWidgetBase> sub_widget = nullptr;
3325 bool fill_sub = false;
3326 nwid_begin = MakeNWidget(nwid_begin, nwid_end, sub_widget, fill_sub);
3327
3328 /* Break out of loop when end reached */
3329 if (sub_widget == nullptr) break;
3330
3331 /* If sub-widget is a container, recursively fill that container. */
3332 if (fill_sub && IsContainerWidgetType(sub_widget->type)) {
3333 nwid_begin = MakeWidgetTree(nwid_begin, nwid_end, sub_widget);
3334 }
3335
3336 /* Add sub_widget to parent container if available, otherwise return the widget to the caller. */
3337 if (nwid_cont != nullptr) nwid_cont->Add(std::move(sub_widget));
3338 if (nwid_parent != nullptr) nwid_parent->Add(std::move(sub_widget));
3339 if (nwid_cont == nullptr && nwid_parent == nullptr) {
3340 parent = std::move(sub_widget);
3341 return nwid_begin;
3342 }
3343 }
3344
3345 if (nwid_begin == nwid_end) return nwid_begin; // Reached the end of the array of parts?
3346
3347 assert(nwid_begin < nwid_end);
3348 assert(nwid_begin->type == WPT_ENDCONTAINER);
3349 return std::next(nwid_begin); // *nwid_begin is also 'used'
3350}
3351
3359std::unique_ptr<NWidgetBase> MakeNWidgets(std::span<const NWidgetPart> nwid_parts, std::unique_ptr<NWidgetBase> &&container)
3360{
3361 if (container == nullptr) container = std::make_unique<NWidgetVertical>();
3362 [[maybe_unused]] auto nwid_part = MakeWidgetTree(std::begin(nwid_parts), std::end(nwid_parts), container);
3363#ifdef WITH_ASSERT
3364 if (nwid_part != std::end(nwid_parts)) [[unlikely]] throw std::runtime_error("Did not consume all NWidgetParts");
3365#endif
3366 return std::move(container);
3367}
3368
3378std::unique_ptr<NWidgetBase> MakeWindowNWidgetTree(std::span<const NWidgetPart> nwid_parts, NWidgetStacked **shade_select)
3379{
3380 auto nwid_begin = std::begin(nwid_parts);
3381 auto nwid_end = std::end(nwid_parts);
3382
3383 *shade_select = nullptr;
3384
3385 /* Read the first widget recursively from the array. */
3386 std::unique_ptr<NWidgetBase> nwid = nullptr;
3387 nwid_begin = MakeWidgetTree(nwid_begin, nwid_end, nwid);
3388 assert(nwid != nullptr);
3389
3390 NWidgetHorizontal *hor_cont = dynamic_cast<NWidgetHorizontal *>(nwid.get());
3391
3392 auto root = std::make_unique<NWidgetVertical>();
3393 root->Add(std::move(nwid));
3394 if (nwid_begin == nwid_end) return root; // There is no body at all.
3395
3396 if (hor_cont != nullptr && hor_cont->GetWidgetOfType(WWT_CAPTION) != nullptr && hor_cont->GetWidgetOfType(WWT_SHADEBOX) != nullptr) {
3397 /* If the first widget has a title bar and a shade box, silently add a shade selection widget in the tree. */
3398 auto shade_stack = std::make_unique<NWidgetStacked>(-1);
3399 *shade_select = shade_stack.get();
3400 /* Load the remaining parts into the shade stack. */
3401 shade_stack->Add(MakeNWidgets({nwid_begin, nwid_end}, std::make_unique<NWidgetVertical>()));
3402 root->Add(std::move(shade_stack));
3403 return root;
3404 }
3405
3406 /* Load the remaining parts into 'root'. */
3407 return MakeNWidgets({nwid_begin, nwid_end}, std::move(root));
3408}
3409
3420std::unique_ptr<NWidgetBase> MakeCompanyButtonRows(WidgetID widget_first, WidgetID widget_last, Colours button_colour, int max_length, StringID button_tooltip, bool resizable)
3421{
3422 assert(max_length >= 1);
3423 std::unique_ptr<NWidgetVertical> vert = nullptr; // Storage for all rows.
3424 std::unique_ptr<NWidgetHorizontal> hor = nullptr; // Storage for buttons in one row.
3425 int hor_length = 0;
3426
3427 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON, nullptr, ZOOM_LVL_NORMAL);
3428 sprite_size.width += WidgetDimensions::unscaled.matrix.Horizontal();
3429 sprite_size.height += WidgetDimensions::unscaled.matrix.Vertical();
3430
3431 for (WidgetID widnum = widget_first; widnum <= widget_last; widnum++) {
3432 /* Ensure there is room in 'hor' for another button. */
3433 if (hor_length == max_length) {
3434 if (vert == nullptr) vert = std::make_unique<NWidgetVertical>();
3435 vert->Add(std::move(hor));
3436 hor = nullptr;
3437 hor_length = 0;
3438 }
3439 if (hor == nullptr) {
3440 hor = std::make_unique<NWidgetHorizontal>();
3441 hor_length = 0;
3442 }
3443
3444 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, button_colour, widnum);
3445 panel->SetMinimalSize(sprite_size.width, sprite_size.height);
3446 panel->SetFill(1, 1);
3447 if (resizable) panel->SetResize(1, 0);
3448 panel->SetToolTip(button_tooltip);
3449 hor->Add(std::move(panel));
3450 hor_length++;
3451 }
3452 if (vert == nullptr) return hor; // All buttons fit in a single row.
3453
3454 if (hor_length > 0 && hor_length < max_length) {
3455 /* Last row is partial, add a spacer at the end to force all buttons to the left. */
3456 auto spc = std::make_unique<NWidgetSpacer>(sprite_size.width, sprite_size.height);
3457 spc->SetFill(1, 1);
3458 if (resizable) spc->SetResize(1, 0);
3459 hor->Add(std::move(spc));
3460 }
3461 if (hor != nullptr) vert->Add(std::move(hor));
3462 return vert;
3463}
Class for backupping variables and making sure they are restored later.
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr Timpl & Set()
Set all bits.
constexpr bool Any(const Timpl &other) const
Test if any of the given values are set.
Enum-as-bit-set wrapper.
Nested widget with a child.
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:2359
NWidgetBase * GetWidgetOfType(WidgetType tp) override
Retrieve a widget by its type.
Definition widget.cpp:2369
void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override
Assign size and position to the widget.
Definition widget.cpp:2305
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2240
NWidgetBackground(WidgetType tp, Colours colour, WidgetID index, std::unique_ptr< NWidgetPIPContainer > &&child=nullptr)
Constructor parent nested widgets.
Definition widget.cpp:2172
void SetPIP(uint8_t pip_pre, uint8_t pip_inter, uint8_t pip_post)
Set additional pre/inter/post space for the background widget.
Definition widget.cpp:2206
std::unique_ptr< NWidgetPIPContainer > child
Child widget.
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2323
void Add(std::unique_ptr< NWidgetBase > &&nwid)
Add a child to the parent.
Definition widget.cpp:2187
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:2317
void SetPIPRatio(uint8_t pip_ratio_pre, uint8_t pip_ratio_inter, uint8_t pip_ratio_post)
Set additional pre/inter/post space ratios for the background widget.
Definition widget.cpp:2225
Baseclass for nested widgets.
void StoreSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height)
Store size and position.
float aspect_ratio
Desired aspect ratio of widget.
virtual void SetupSmallestSize(Window *w)=0
Compute smallest size needed by the widget.
virtual void SetDirty(const Window *w) const
Mark the widget as 'dirty' (in need of repaint).
Definition widget.cpp:905
WidgetType type
Type of the widget / nested widget.
uint resize_x
Horizontal resize step (0 means not resizable).
uint fill_x
Horizontal fill stepsize (from initial size, 0 means not resizable).
NWidgetBase * parent
Parent widget of this widget, automatically filled in when added to container.
virtual void FillWidgetLookup(WidgetLookup &widget_lookup)=0
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
RectPadding uz_padding
Unscaled padding, for resize calculation.
uint smallest_x
Smallest horizontal size of the widget in a filled window.
AspectFlags aspect_flags
Which dimensions can be resized.
virtual void Draw(const Window *w)=0
Draw the widgets of the tree.
uint current_x
Current horizontal size (after resizing).
int pos_y
Vertical position of top-left corner of the widget in the window.
int pos_x
Horizontal position of top-left corner of the widget in the window.
void SetPadding(uint8_t top, uint8_t right, uint8_t bottom, uint8_t left)
Set additional space (padding) around the widget.
uint smallest_y
Smallest vertical size of the widget in a filled window.
virtual NWidgetBase * GetWidgetOfType(WidgetType tp)
Retrieve a widget by its type.
Definition widget.cpp:925
uint fill_y
Vertical fill stepsize (from initial size, 0 means not resizable).
uint resize_y
Vertical resize step (0 means not resizable).
RectPadding padding
Padding added to the widget. Managed by parent container widget. (parent container may swap left and ...
uint current_y
Current vertical size (after resizing).
Baseclass for container widgets.
void Add(std::unique_ptr< NWidgetBase > &&wid)
Append widget wid to container.
Definition widget.cpp:1280
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1287
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1294
bool IsEmpty()
Return whether the container is empty.
std::vector< std::unique_ptr< NWidgetBase > > children
Child widgets in container.
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1303
NWidgetBase * GetWidgetOfType(WidgetType tp) override
Retrieve a widget by its type.
Definition widget.cpp:1258
Base class for a 'real' widget.
WidgetData widget_data
Data of the widget.
void SetToolTip(StringID tool_tip)
Set the tool tip of the nested widget.
Definition widget.cpp:1198
bool IsDisabled() const
Return whether the widget is disabled.
void SetSprite(SpriteID sprite)
Set sprite of the nested widget.
Definition widget.cpp:1148
WidgetID GetIndex() const
Get the WidgetID of this nested widget.
Definition widget.cpp:1234
NWidgetDisplayFlags disp_flags
Flags that affect display and interaction with the widget.
void SetTextStyle(TextColour colour, FontSize size)
Set the text style of the nested widget.
Definition widget.cpp:1188
WidgetID GetScrollbarIndex() const
Get the WidgetID of this nested widget's scrollbar.
Definition widget.cpp:1243
void SetAlignment(StringAlignment align)
Set the text/image alignment of the nested widget.
Definition widget.cpp:1216
void SetResizeWidgetType(ResizeWidgetValues type)
Set the resize widget type of the nested widget.
Definition widget.cpp:1178
NWidgetCore(WidgetType tp, Colours colour, WidgetID index, uint fill_x, uint fill_y, const WidgetData &widget_data, StringID tool_tip)
Initialization of a 'real' widget.
Definition widget.cpp:1116
void SetSpriteTip(SpriteID sprite, StringID tool_tip)
Set sprite and tool tip of the nested widget.
Definition widget.cpp:1158
const WidgetID index
Index of the nested widget (-1 means 'not used').
StringAlignment align
Alignment of text/image within widget.
FontSize text_size
Size of text within widget.
StringID GetString() const
Get the string that has been set for this nested widget.
Definition widget.cpp:1225
WidgetID scrollbar_index
Index of an attached scrollbar.
StringID GetToolTip() const
Get the tool tip of the nested widget.
Definition widget.cpp:1207
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1253
void SetString(StringID string)
Set string of the nested widget.
Definition widget.cpp:1128
TextColour text_colour
Colour of text within widget.
Colours colour
Colour of this widget.
void SetMatrixDimension(uint32_t columns, uint32_t rows)
Set the matrix dimension.
Definition widget.cpp:1169
void SetLowered(bool lowered)
Lower or raise the widget.
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1248
void SetStringTip(StringID string, StringID tool_tip)
Set string and tool tip of the nested widget.
Definition widget.cpp:1138
StringID tool_tip
Tooltip of the widget.
bool IsLowered() const
Return whether the widget is lowered.
NWidgetHorizontalLTR(NWidContainerFlags flags={})
Horizontal left-to-right container widget.
Definition widget.cpp:1714
void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override
Assign size and position to the widget.
Definition widget.cpp:1719
Horizontal container.
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1535
NWidgetHorizontal(NWidContainerFlags flags={})
Horizontal container widget.
Definition widget.cpp:1531
void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override
Assign size and position to the widget.
Definition widget.cpp:1604
void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override
Assign size and position to the widget.
Definition widget.cpp:1453
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1431
const WidgetID index
If non-negative, index in the Window::widget_lookup.
Definition widget.cpp:1428
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1471
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2964
static void InvalidateDimensionCache()
Reset the cached dimensions.
Definition widget.cpp:2664
static Dimension resizebox_dimension
Cached size of a resizebox widget.
static Dimension shadebox_dimension
Cached size of a shadebox widget.
static Dimension closebox_dimension
Cached size of a closebox widget.
static Dimension stickybox_dimension
Cached size of a stickybox widget.
NWidgetLeaf(WidgetType tp, Colours colour, WidgetID index, const WidgetData &data, StringID tip)
Nested leaf widget.
Definition widget.cpp:2691
static Dimension defsizebox_dimension
Cached size of a defsizebox widget.
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2797
static Dimension dropdown_dimension
Cached size of a dropdown widget.
static Dimension debugbox_dimension
Cached size of a debugbox widget.
bool ButtonHit(const Point &pt)
For a NWID_BUTTON_DROPDOWN, test whether pt refers to the button or to the drop-down.
Definition widget.cpp:3094
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2086
int count
Amount of valid elements.
void SetClicked(int clicked)
Sets the clicked element in the matrix.
Definition widget.cpp:1949
int GetCurrentElement() const
Get current element.
Definition widget.cpp:1999
const WidgetID index
If non-negative, index in the Window::widget_lookup.
Scrollbar * sb
The scrollbar we're associated with.
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2004
int widget_w
The width of the child widget including inter spacing.
void SetScrollbar(Scrollbar *sb)
Assign a scrollbar to this matrix.
Definition widget.cpp:1990
void GetScrollOffsets(int &start_x, int &start_y, int &base_offs_x, int &base_offs_y)
Get the different offsets that are influenced by scrolling.
Definition widget.cpp:2141
int current_element
The element currently being processed.
int widgets_x
The number of visible widgets in horizontal direction.
int widget_h
The height of the child widget including inter spacing.
void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override
Assign size and position to the widget.
Definition widget.cpp:2026
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:2050
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:2056
int clicked
The currently clicked element.
void SetCount(int count)
Set the number of elements in this matrix.
Definition widget.cpp:1966
int widgets_y
The number of visible widgets in vertical direction.
Colours colour
Colour of this widget.
Container with pre/inter/post child space.
uint8_t gaps
Number of gaps between widgets.
NWidContainerFlags flags
Flags of the container.
uint8_t pip_pre
Amount of space before first widget.
uint8_t uz_pip_pre
Unscaled space before first widget.
uint8_t uz_pip_inter
Unscaled space between widgets.
uint8_t pip_ratio_pre
Ratio of remaining space before first widget.
void SetPIPRatio(uint8_t pip_ratio_pre, uint8_t pip_ratio_inter, uint8_t pip_rato_post)
Set additional pre/inter/post space for the container.
Definition widget.cpp:1523
void SetPIP(uint8_t pip_pre, uint8_t pip_inter, uint8_t pip_post)
Set additional pre/inter/post space for the container.
Definition widget.cpp:1503
uint8_t pip_post
Amount of space after last widget.
uint8_t pip_ratio_inter
Ratio of remaining space between widgets.
uint8_t pip_ratio_post
Ratio of remaining space after last widget.
uint8_t uz_pip_post
Unscaled space after last widget.
uint8_t pip_inter
Amount of space between widgets.
Base class for a resizable nested widget.
uint8_t uz_text_spacing
'Unscaled' text padding, stored for resize calculation.
bool absolute
Set if minimum size is fixed and should not be resized.
void SetMinimalSize(uint min_x, uint min_y)
Set minimal size of the widget.
Definition widget.cpp:997
bool UpdateSize(uint min_x, uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1080
uint min_x
Minimal horizontal size of only this widget.
FontSize uz_text_size
'Unscaled' font size, stored for resize calculation.
NWidgetResizeBase(WidgetType tp, uint fill_x, uint fill_y)
Constructor for resizable nested widgets.
Definition widget.cpp:955
uint uz_min_x
Unscaled Minimal horizontal size of only this widget.
uint8_t uz_text_lines
'Unscaled' text lines, stored for resize calculation.
void SetFill(uint fill_x, uint fill_y)
Set the filling of the widget from initial size.
Definition widget.cpp:1036
void SetMinimalTextLines(uint8_t min_lines, uint8_t spacing, FontSize size)
Set minimal text lines for the widget.
Definition widget.cpp:1023
uint min_y
Minimal vertical size of only this widget.
uint uz_min_y
Unscaled Minimal vertical size of only this widget.
bool UpdateMultilineWidgetSize(const std::string &str, int max_lines)
Try to set optimum widget size for a multiline text widget.
Definition widget.cpp:1060
void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override
Assign size and position to the widget.
Definition widget.cpp:1101
bool UpdateVerticalSize(uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1094
void SetResize(uint resize_x, uint resize_y)
Set resize step of the widget.
Definition widget.cpp:1047
void SetAspect(float ratio, AspectFlags flags=AspectFlag::ResizeX)
Set desired aspect ratio of this widget.
Definition widget.cpp:966
void SetMinimalSizeAbsolute(uint min_x, uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1010
Nested widget to display and control a scrollbar in a window.
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2608
NWidgetScrollbar(WidgetType tp, Colours colour, WidgetID index)
Scrollbar widget.
Definition widget.cpp:2566
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2587
static Dimension horizontal_dimension
Cached size of horizontal scrollbar button.
static Dimension vertical_dimension
Cached size of vertical scrollbar button.
NWidgetSpacer(int width, int height)
Generic spacer widget.
Definition widget.cpp:1901
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1914
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1935
void SetDirty(const Window *w) const override
Mark the widget as 'dirty' (in need of repaint).
Definition widget.cpp:1930
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1918
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1907
Stacked widgets, widgets all occupying the same space in the window.
int shown_plane
Plane being displayed (for NWID_SELECTION only).
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1314
const WidgetID index
If non-negative, index in the Window::widget_lookup.
WidgetLookup * widget_lookup
Window's widget lookup, updated in SetDisplayedPlane().
void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override
Assign size and position to the widget.
Definition widget.cpp:1355
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1386
bool SetDisplayedPlane(int plane)
Select which plane to show (for NWID_SELECTION only).
Definition widget.cpp:1410
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1375
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1395
NWidgetVertical(NWidContainerFlags flags={})
Vertical container widget.
Definition widget.cpp:1725
void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override
Assign size and position to the widget.
Definition widget.cpp:1798
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1729
void UpdateViewportCoordinates(Window *w)
Update the position and size of the viewport (after eg a resize).
Definition widget.cpp:2424
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2388
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2381
void InitializeViewport(Window *w, std::variant< TileIndex, VehicleID > focus, ZoomLevel zoom)
Initialize the viewport of the window.
Definition widget.cpp:2415
Scrollbar data structure.
size_type GetCapacity() const
Gets the number of visible elements of the scrollbar.
void SetCount(size_t num)
Sets the number of elements in the list.
bool IsVertical() const
Is the scrollbar vertical or not?
bool UpdatePosition(int difference, ScrollbarStepping unit=SS_SMALL)
Updates the position of the first visible element by the given amount.
void SetCapacity(size_t capacity)
Set the capacity of visible elements.
size_type GetScrolledRowFromWidget(int clickpos, const Window *const w, WidgetID widget, int padding=0, int line_height=-1) const
Compute the row of a scrolled widget that a user clicked in.
Definition widget.cpp:2447
void SetCapacityFromWidget(Window *w, WidgetID widget, int padding=0)
Set capacity of visible elements from the size and resize properties of a widget.
Definition widget.cpp:2521
size_type GetCount() const
Gets the number of elements in the list.
EventState UpdateListPositionOnKeyPress(int &list_position, uint16_t keycode) const
Update the given list position as if it were on this scroll bar when the given keycode was pressed.
Definition widget.cpp:2468
void ScrollTowards(size_type position)
Scroll towards the given position; if the item is visible nothing happens, otherwise it will be shown...
void SetStepSize(size_t stepsize)
Set the distance to scroll when using the buttons or the wheel.
size_type pos
Index of first visible item of the list.
size_type GetPosition() const
Gets the position of the first visible element in the list.
@ SS_BIG
Step in cap units.
static constexpr uint WD_CAPTION_HEIGHT
Minimum height of a title bar.
Definition window_gui.h:88
RectPadding defsizebox
Padding around image in defsizebox widget.
Definition window_gui.h:46
RectPadding closebox
Padding around image in closebox widget.
Definition window_gui.h:48
RectPadding framerect
Standard padding inside many panels.
Definition window_gui.h:40
static constexpr uint WD_DROPDOWN_HEIGHT
Minimum height of a drop down widget.
Definition window_gui.h:89
RectPadding captiontext
Padding for text within caption widget.
Definition window_gui.h:49
RectPadding debugbox
Padding around image in debugbox widget.
Definition window_gui.h:45
RectPadding frametext
Padding inside frame with text.
Definition window_gui.h:41
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:29
RectPadding modalpopup
Spacing for popup warning/information windows.
Definition window_gui.h:52
RectPadding hscrollbar
Padding inside horizontal scrollbar buttons.
Definition window_gui.h:37
RectPadding shadebox
Padding around image in shadebox widget.
Definition window_gui.h:43
RectPadding resizebox
Padding around image in resizebox widget.
Definition window_gui.h:47
RectPadding vscrollbar
Padding inside vertical scrollbar buttons.
Definition window_gui.h:36
RectPadding imgbtn
Padding around image button image.
Definition window_gui.h:34
int vsep_normal
Normal vertical spacing.
Definition window_gui.h:58
int vsep_wide
Wide vertical spacing.
Definition window_gui.h:60
int hsep_wide
Wide horizontal spacing.
Definition window_gui.h:62
static constexpr uint WD_CLOSEBOX_WIDTH
Minimum width of a close box widget.
Definition window_gui.h:87
RectPadding fullbevel
Always-scaled bevel thickness.
Definition window_gui.h:39
RectPadding inset
Padding inside inset container.
Definition window_gui.h:35
static const WidgetDimensions unscaled
Unscaled widget dimensions.
Definition window_gui.h:94
static constexpr uint WD_RESIZEBOX_WIDTH
Minimum width of a resize box widget.
Definition window_gui.h:86
static constexpr uint WD_STICKYBOX_WIDTH
Minimum width of a standard sticky box widget.
Definition window_gui.h:83
RectPadding matrix
Padding of WWT_MATRIX items.
Definition window_gui.h:42
int hsep_normal
Normal horizontal spacing.
Definition window_gui.h:61
RectPadding dropdownlist
Padding of complete drop down list.
Definition window_gui.h:51
static constexpr uint WD_DEBUGBOX_WIDTH
Minimum width of a standard debug box widget.
Definition window_gui.h:84
RectPadding stickybox
Padding around image in stickybox widget.
Definition window_gui.h:44
static constexpr uint WD_DEFSIZEBOX_WIDTH
Minimum width of a standard defsize box widget.
Definition window_gui.h:85
RectPadding bevel
Bevel thickness, affected by "scaled bevels" game option.
Definition window_gui.h:38
RectPadding dropdowntext
Padding of drop down list item.
Definition window_gui.h:50
static constexpr uint WD_SHADEBOX_WIDTH
Distances used in drawing widgets.
Definition window_gui.h:82
int hsep_indent
Width of indentation for tree layouts.
Definition window_gui.h:63
ReferenceThroughBaseContainer< std::array< Colours, MAX_COMPANIES > > _company_colours
NOSAVE: can be determined from company structs.
Functions related to companies.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:77
Dimension maxdim(const Dimension &d1, const Dimension &d2)
Compute bounding box of both dimensions.
Geometry functions.
int GetStringHeight(std::string_view str, int maxw, FontSize fontsize)
Calculates height of string (in pixels).
Definition gfx.cpp:705
Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
Get the size of a sprite.
Definition gfx.cpp:923
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:852
void DrawRectOutline(const Rect &r, int colour, int width, int dash)
Draw the outline of a Rect.
Definition gfx.cpp:457
int DrawString(int left, int right, int top, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition gfx.cpp:658
void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition gfx.cpp:115
void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub, ZoomLevel zoom)
Draw a sprite, not in a viewport.
Definition gfx.cpp:989
bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
Set up a clipping area for only drawing into a certain area.
Definition gfx.cpp:1519
Dimension GetScaledSpriteSize(SpriteID sprid)
Scale sprite size for GUI.
Definition widget.cpp:67
int CenterBounds(int min, int max, int size)
Determine where to draw a centred object inside a widget.
Definition gfx_func.h:166
uint32_t SpriteID
The number of a sprite, without mapping bits and colourtables.
Definition gfx_type.h:17
FontSize
Available font sizes.
Definition gfx_type.h:242
@ FS_BEGIN
First font.
Definition gfx_type.h:249
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:243
StringAlignment
How to align the to-be drawn text.
Definition gfx_type.h:374
@ SA_TOP
Top align the text.
Definition gfx_type.h:380
@ SA_LEFT
Left align the text.
Definition gfx_type.h:375
@ SA_HOR_MASK
Mask for horizontal alignment.
Definition gfx_type.h:378
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:377
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:376
@ SA_VERT_MASK
Mask for vertical alignment.
Definition gfx_type.h:383
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition gfx_type.h:387
@ SA_BOTTOM
Bottom align the text.
Definition gfx_type.h:382
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:385
@ SA_VERT_CENTER
Vertically center the text.
Definition gfx_type.h:381
uint32_t PaletteID
The number of the palette.
Definition gfx_type.h:18
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:294
@ FILLRECT_CHECKER
Draw only every second pixel, used for greying-out.
Definition gfx_type.h:333
@ FILLRECT_RECOLOUR
Apply a recolour sprite to the screen content.
Definition gfx_type.h:334
std::unique_ptr< NWidgetBase > MakeWindowNWidgetTree(std::span< const NWidgetPart > nwid_parts, NWidgetStacked **shade_select)
Make a nested widget tree for a window from a parts array.
Definition widget.cpp:3378
std::unique_ptr< NWidgetBase > MakeNWidgets(std::span< const NWidgetPart > nwid_parts, std::unique_ptr< NWidgetBase > &&container)
Construct a nested widget tree from an array of parts.
Definition widget.cpp:3359
constexpr NWidgetPart SetAlignment(StringAlignment align)
Widget part function for setting the alignment of text/images.
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
Definition window.cpp:943
void AddDirtyBlock(int left, int top, int right, int bottom)
Extend the internal _invalid_rect rectangle to contain the rectangle defined by the given parameters.
Definition gfx.cpp:1467
constexpr bool IsInsideBS(const T x, const size_t base, const size_t size)
Checks if a value is between a window started at some base point.
constexpr uint CeilDiv(uint a, uint b)
Computes ceil(a / b) for non-negative a and b.
constexpr T Clamp(const T a, const T min, const T max)
Clamp a value between an interval.
Definition math_func.hpp:79
uint8_t GetColourGradient(Colours colour, ColourShade shade)
Get colour gradient palette index.
Definition palette.cpp:387
static const uint8_t PC_WHITE
White palette colour.
static const uint8_t PC_BLACK
Black palette colour.
Base for the GUIs that have an edit box in them.
A number of safeguards to prevent using unsafe methods.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:57
Types related to global configuration settings.
This file contains all sprite-related enums and defines.
static constexpr uint8_t PALETTE_TEXT_RECOLOUR
Set if palette is actually a magic text recolour.
Definition sprites.h:1536
static const PaletteID PALETTE_TO_TRANSPARENT
This sets the sprite to transparent.
Definition sprites.h:1606
static const PaletteID PALETTE_NEWSPAPER
Recolour sprite for newspaper-greying.
Definition sprites.h:1608
Definition of base types and functions in a cross-platform compatible way.
The colour translation of GRF's strings.
static const uint8_t _string_colourmap[17]
Colour mapping for TextColour.
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:426
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:56
Functions related to OTTD's strings.
Types related to strings.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
@ TD_LTR
Text is written left-to-right by default.
@ TD_RTL
Text is written right-to-left by default.
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
GUISettings gui
settings related to the GUI
Point pos
logical mouse position
Definition gfx_type.h:124
Dimensions (a width and height) of a rectangle in 2D.
Data about how and where to blit pixels.
Definition gfx_type.h:156
bool scale_bevels
bevels are scaled with GUI scale.
bool newgrf_developer_tools
activate NewGRF developer tools and allow modifying NewGRFs in an existing game
StringAlignment align
Alignment of text/image.
WidgetData data
Data value of the widget.
StringID tooltip
Tooltip of the widget.
uint8_t post
Amount of space before/between/after child widgets.
uint8_t lines
Number of text lines.
uint8_t spacing
Extra spacing around lines.
FontSize size
Font size of text lines.
TextColour colour
TextColour for DrawString.
FontSize size
Font size of text.
Colours colour
Widget colour.
WidgetID index
Index of the widget.
Partial widget specification to allow NWidgets to be written nested.
WidgetType type
Type of the part.
Coordinates of a point in 2D.
Data stored about a string that can be modified in the GUI.
Padding dimensions to apply to each side of a Rect.
constexpr uint Horizontal() const
Get total horizontal padding of RectPadding.
constexpr uint Vertical() const
Get total vertical padding of RectPadding.
Specification of a rectangle with absolute coordinates of all edges.
Rect WithWidth(int width, bool end) const
Copy Rect and set its width.
int Width() const
Get width of Rect.
Rect Shrink(int s) const
Copy and shrink Rect by s pixels.
Rect WithHeight(int height, bool end=false) const
Copy Rect and set its height.
int Height() const
Get height of Rect.
Rect Expand(int s) const
Copy and expand Rect by s pixels.
Data structure for viewport, display of a part of the world.
int top
Screen coordinate top edge of the viewport.
int width
Screen width of the viewport.
ZoomLevel zoom
The zoom level of the viewport.
int virtual_width
width << zoom
int left
Screen coordinate left edge of the viewport.
int height
Screen height of the viewport.
int virtual_height
height << zoom
Container with the data associated to a single widget.
Data structure for an opened window.
Definition window_gui.h:274
virtual void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize)
Update size and resize step of a widget in the window.
Definition window_gui.h:619
static int SortButtonWidth()
Get width of up/down arrow of sort button state.
Definition widget.cpp:793
void DrawWidgets() const
Paint all widgets of a window.
Definition widget.cpp:744
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:502
WidgetID mouse_capture_widget
ID of current mouse capture widget (e.g. dragged scrollbar). -1 if no widget has mouse capture.
Definition window_gui.h:327
void DrawSortButtonState(WidgetID widget, SortButtonState state) const
Draw a sort button's up or down arrow symbol.
Definition widget.cpp:776
virtual bool IsNewGRFInspectable() const
Is the data related to this window NewGRF inspectable?
Definition window_gui.h:850
ViewportData * viewport
Pointer to viewport data, if present.
Definition window_gui.h:319
void DrawViewport() const
Draw the viewport of this window.
virtual void DrawWidget(const Rect &r, WidgetID widget) const
Draw the contents of a nested widget.
Definition window_gui.h:605
Owner owner
The owner of the content shown in this window. Company colour is acquired from this variable.
Definition window_gui.h:317
int left
x position of left edge of the window
Definition window_gui.h:310
bool IsShaded() const
Is window shaded currently?
Definition window_gui.h:558
int top
y position of top edge of the window
Definition window_gui.h:311
const QueryString * GetQueryString(WidgetID widnum) const
Return the querystring associated to a editbox.
Definition window.cpp:331
WidgetLookup widget_lookup
Indexed access to the nested widget tree. Do not access directly, use Window::GetWidget() instead.
Definition window_gui.h:323
int GetRowFromWidget(int clickpos, WidgetID widget, int padding, int line_height=-1) const
Compute the row of a widget that a user clicked in.
Definition window.cpp:210
const NWID * GetWidget(WidgetID widnum) const
Get the nested widget with number widnum from the nested widget tree.
Definition window_gui.h:973
WindowFlags flags
Window flags.
Definition window_gui.h:301
std::unique_ptr< NWidgetBase > nested_root
Root of the nested tree.
Definition window_gui.h:322
int height
Height of the window (number of pixels down in y direction)
Definition window_gui.h:313
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:312
Functions related to transparency.
TransparencyOptionBits _transparency_opt
The bits that should be transparent.
uint TransparencyOptionBits
transparency option bits
@ TO_TEXT
loading and cost/income text
@ TO_SIGNS
signs
NWidgetPartTextLines text_lines
Part with text line data.
NWidgetPartPIP pip
Part with pre/inter/post spaces.
NWidgetPartPaddings padding
Part with paddings.
NWidgetFunctionType * func_ptr
Part with a function call.
NWidContainerFlags cont_flags
Part with container flags.
NWidgetPartDataTip data_tip
Part with a data/tooltip.
NWidgetPartAlignment align
Part with internal alignment.
NWidgetPartAspect aspect
Part to set aspect ratio.
NWidgetPartTextStyle text_style
Part with text style data.
Point xy
Part with an x/y size.
NWidgetPartWidget widget
Part with a start of a widget.
void InitializeWindowViewport(Window *w, int x, int y, int width, int height, std::variant< TileIndex, VehicleID > focus, ZoomLevel zoom)
Initialize viewport of the window for use.
Definition viewport.cpp:224
Functions related to (drawing on) viewports.
static void DrawCloseBox(const Rect &r, Colours colour)
Draw a close box.
Definition widget.cpp:671
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:296
Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
Apply 'scroll' to a rect to be drawn in.
Definition widget.cpp:2538
void ApplyNWidgetPartAttribute(const NWidgetPart &nwid, NWidgetBase *dest)
Apply an attribute NWidgetPart to an NWidget.
Definition widget.cpp:3123
static void DrawDefSizeBox(const Rect &r, Colours colour, bool clicked)
Draw a defsize box.
Definition widget.cpp:632
bool IsContainerWidgetType(WidgetType tp)
Test if WidgetType is a container widget.
Definition widget.cpp:3302
static void DrawDebugBox(const Rect &r, Colours colour, bool clicked)
Draw a NewGRF debug box.
Definition widget.cpp:643
static void DrawStickyBox(const Rect &r, Colours colour, bool clicked)
Draw a sticky box.
Definition widget.cpp:621
static void DrawFrame(const Rect &r, Colours colour, TextColour text_colour, std::string_view str, StringAlignment align, FontSize fs)
Draw a frame widget.
Definition widget.cpp:557
static Point GetAlignedPosition(const Rect &r, const Dimension &d, StringAlignment align)
Calculate x and y coordinates for an aligned object within a window.
Definition widget.cpp:119
static void DrawMatrix(const Rect &r, Colours colour, bool clicked, uint32_t num_columns, uint32_t num_rows, uint resize_x, uint resize_y)
Draw a matrix widget.
Definition widget.cpp:419
static void DrawHorizontalScrollbar(const Rect &r, Colours colour, bool left_clicked, bool bar_dragged, bool right_clicked, const Scrollbar *scrollbar)
Draw a horizontal scrollbar.
Definition widget.cpp:517
static void DrawButtonDropdown(const Rect &r, Colours colour, bool clicked_button, bool clicked_dropdown, std::string_view str, StringAlignment align)
Draw a button with a dropdown (WWT_DROPDOWN and NWID_BUTTON_DROPDOWN).
Definition widget.cpp:722
Dimension GetScaledSpriteSize(SpriteID sprid)
Scale sprite size for GUI.
Definition widget.cpp:67
static std::unique_ptr< NWidgetBase > MakeNWidget(const NWidgetPart &nwid)
Make NWidget from an NWidgetPart.
Definition widget.cpp:3231
void SetupWidgetDimensions()
Set up pre-scaled versions of Widget Dimensions.
Definition widget.cpp:79
void DrawCaption(const Rect &r, Colours colour, Owner owner, TextColour text_colour, std::string_view str, StringAlignment align, FontSize fs)
Draw a caption bar.
Definition widget.cpp:692
static void DrawShadeBox(const Rect &r, Colours colour, bool clicked)
Draw a shade box.
Definition widget.cpp:610
WidgetID GetWidgetFromPos(const Window *w, int x, int y)
Returns the index for the widget located at the given position relative to the window.
Definition widget.cpp:281
static void DrawImageButtons(const Rect &r, WidgetType type, Colours colour, bool clicked, SpriteID img, StringAlignment align)
Draw an image button.
Definition widget.cpp:351
static bool IsAttributeWidgetPartType(WidgetType tp)
Test if (an NWidgetPart) WidgetType is an attribute widget part type.
Definition widget.cpp:3112
static void DrawLabel(const Rect &r, TextColour colour, std::string_view str, StringAlignment align, FontSize fs)
Draw the label-part of a widget.
Definition widget.cpp:368
void ScrollbarClickHandler(Window *w, NWidgetCore *nw, int x, int y)
Special handling for the scrollbar widget type.
Definition widget.cpp:257
static void DrawResizeBox(const Rect &r, Colours colour, bool at_left, bool clicked, bool bevel)
Draw a resize box.
Definition widget.cpp:656
static Point HandleScrollbarHittest(const Scrollbar *sb, int top, int bottom, bool horizontal)
Compute the vertical position of the draggable part of scrollbar.
Definition widget.cpp:148
static void DrawText(const Rect &r, TextColour colour, std::string_view str, StringAlignment align, FontSize fs)
Draw text.
Definition widget.cpp:385
static void ScrollbarClickPositioning(Window *w, NWidgetScrollbar *sb, int x, int y, int mi, int ma)
Compute new position of the scrollbar after a click and updates the window flags.
Definition widget.cpp:193
static void DrawInset(const Rect &r, Colours colour, TextColour text_colour, std::string_view str, StringAlignment align, FontSize fs)
Draw an inset widget.
Definition widget.cpp:403
static std::span< constNWidgetPart >::iterator MakeWidgetTree(std::span< const NWidgetPart >::iterator nwid_begin, std::span< const NWidgetPart >::iterator nwid_end, std::unique_ptr< NWidgetBase > &parent)
Build a nested widget tree by recursively filling containers with nested widgets read from their part...
Definition widget.cpp:3315
static void DrawVerticalScrollbar(const Rect &r, Colours colour, bool up_clicked, bool bar_dragged, bool down_clicked, const Scrollbar *scrollbar)
Draw a vertical scrollbar.
Definition widget.cpp:477
static RectPadding ScaleGUITrad(const RectPadding &r)
Scale a RectPadding to GUI zoom level.
Definition widget.cpp:48
std::unique_ptr< NWidgetBase > MakeCompanyButtonRows(WidgetID widget_first, WidgetID widget_last, Colours button_colour, int max_length, StringID button_tooltip, bool resizable)
Make a number of rows with button-like graphics, for enabling/disabling each company.
Definition widget.cpp:3420
WidgetType
Window widget types, nested widget types, and nested widget part types.
Definition widget_type.h:36
@ WWT_PUSHTXTBTN
Normal push-button (no toggle button) with text caption.
@ WWT_INSET
Pressed (inset) panel, most commonly used as combo box text area.
Definition widget_type.h:41
@ WPT_FILL
Widget part for specifying fill.
Definition widget_type.h:83
@ WPT_ALIGNMENT
Widget part for specifying text/image alignment.
Definition widget_type.h:89
@ WWT_IMGBTN
(Toggle) Button with image
Definition widget_type.h:42
@ WWT_PUSHBTN
Normal push-button (no toggle button) with custom drawing.
@ WWT_IMGBTN_2
(Toggle) Button with diff image when clicked
Definition widget_type.h:43
@ WWT_PUSHIMGBTN
Normal push-button (no toggle button) with image caption.
@ WPT_MINSIZE
Widget part for specifying minimal size.
Definition widget_type.h:81
@ WWT_PUSHARROWBTN
Normal push-button (no toggle button) with arrow caption.
@ WWT_LABEL
Centered label.
Definition widget_type.h:47
@ NWID_BUTTON_DROPDOWN
Button with a drop-down.
Definition widget_type.h:73
@ NWID_SPACER
Invisible widget that takes some space.
Definition widget_type.h:69
@ WWT_EDITBOX
a textbox for typing
Definition widget_type.h:61
@ WWT_ARROWBTN
(Toggle) Button with an arrow
Definition widget_type.h:44
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:65
@ WWT_TEXTBTN
(Toggle) Button with text
Definition widget_type.h:45
@ WPT_SCROLLBAR
Widget part for attaching a scrollbar.
Definition widget_type.h:90
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:40
@ WPT_ASPECT
Widget part for specifying aspect ratio.
Definition widget_type.h:91
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition widget_type.h:56
@ WPT_RESIZE
Widget part for specifying resizing.
Definition widget_type.h:80
@ WWT_MATRIX
Grid of rows and columns.
Definition widget_type.h:49
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition widget_type.h:54
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition widget_type.h:51
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:75
@ WPT_TEXTSTYLE
Widget part for specifying text colour.
Definition widget_type.h:88
@ WPT_PADDING
Widget part for specifying a padding.
Definition widget_type.h:85
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:67
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition widget_type.h:59
@ WWT_TEXTBTN_2
(Toggle) Button with diff text when clicked
Definition widget_type.h:46
@ WWT_FRAME
Frame.
Definition widget_type.h:50
@ WPT_MINTEXTLINES
Widget part for specifying minimal number of lines of text.
Definition widget_type.h:82
@ WWT_EMPTY
Empty widget, place holder to reserve space in widget tree.
Definition widget_type.h:38
@ WWT_LAST
Last Item. use WIDGETS_END to fill up padding!!
Definition widget_type.h:62
@ WPT_ATTRIBUTE_END
End marker for attribute NWidgetPart types.
Definition widget_type.h:92
@ NWID_HSCROLLBAR
Horizontal scrollbar.
Definition widget_type.h:74
@ WPT_ENDCONTAINER
Widget part to denote end of a container.
Definition widget_type.h:95
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition widget_type.h:58
@ WPT_PIPRATIO
Widget part for specifying pre/inter/post ratio for containers.
Definition widget_type.h:87
@ WPT_DATATIP
Widget part for specifying data and tooltip.
Definition widget_type.h:84
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX)
Definition widget_type.h:55
@ WPT_ATTRIBUTE_BEGIN
Begin marker for attribute NWidgetPart types.
Definition widget_type.h:79
@ WPT_PIPSPACE
Widget part for specifying pre/inter/post space for containers.
Definition widget_type.h:86
@ NWID_MATRIX
Matrix container.
Definition widget_type.h:68
@ NWID_VIEWPORT
Nested widget containing a viewport.
Definition widget_type.h:72
@ WWT_DROPDOWN
Drop down list.
Definition widget_type.h:60
@ WWT_TEXT
Pure simple text.
Definition widget_type.h:48
@ NWID_HORIZONTAL_LTR
Horizontal container that doesn't change the order of the widgets for RTL languages.
Definition widget_type.h:66
@ WPT_FUNCTION
Widget part for calling a user function.
Definition widget_type.h:94
@ WWT_DEBUGBOX
NewGRF debug box (at top-right of a window, between WWT_CAPTION and WWT_SHADEBOX)
Definition widget_type.h:53
@ NWID_LAYER
Layered widgets, all visible together.
Definition widget_type.h:71
@ NWID_SELECTION
Stacked widgets, only one visible at a time (eg in a panel with tabs).
Definition widget_type.h:70
@ ScrollbarDown
Down-button is lowered bit.
@ ShadeDimmed
Display dimmed colours in the viewport.
@ ScrollbarUp
Up-button is lowered bit.
@ DropdownActive
Dropdown menu of the button dropdown widget is active.
@ NoTransparency
Viewport is never transparent.
uint ComputeMaxSize(uint base, uint max_space, uint step)
Return the biggest possible size of a nested widget.
@ SZSP_HORIZONTAL
Display plane with zero size vertically, and filling and resizing horizontally.
@ SZSP_BEGIN
First zero-size plane.
@ SZSP_VERTICAL
Display plane with zero size horizontally, and filling and resizing vertically.
NWidContainerFlag
Nested widget container flags,.
@ EqualSize
Containers should keep all their (resizing) children equally large.
@ BigFirst
Allocate space to biggest resize first.
SizingType
Different forms of sizing nested widgets, using NWidgetBase::AssignSizePosition()
@ ST_RESIZE
Resize the nested widget tree.
@ ST_SMALLEST
Initialize nested widget tree to smallest size. Also updates current_x and current_y.
@ AWV_LEFT
Force the arrow to the left.
Definition widget_type.h:23
@ AWV_RIGHT
Force the arrow to the right.
Definition widget_type.h:24
@ AWV_DECREASE
Arrow to the left or in case of RTL to the right.
Definition widget_type.h:21
@ AWV_INCREASE
Arrow to the right or in case of RTL to the left.
Definition widget_type.h:22
std::map< WidgetID, class NWidgetBase * > WidgetLookup
Lookup between widget IDs and NWidget objects.
ResizeWidgetValues
WidgetData values for a resize box widget.
Definition widget_type.h:28
@ RWV_SHOW_BEVEL
Bevel of resize box is shown.
Definition widget_type.h:29
bool _window_highlight_colour
If false, highlight is white, otherwise the by the widget defined colour.
Definition window.cpp:75
Functions, definitions and such used only by the GUI.
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:296
@ Transparent
Makes the background transparent if set.
@ BorderOnly
Draw border only, no background.
@ Darkened
If set the background is darker, allows for lowered frames with normal background colour when used wi...
@ Lowered
If set the frame is lowered and the background colour brighter (ie. buttons when pressed)
@ SizingLeft
Window is being resized towards the left.
@ Highlighted
Window has a widget that has a highlight.
@ SizingRight
Window is being resized towards the right.
@ WhiteBorder
Window white border counter bit mask.
@ Sticky
Window is made sticky by user.
SortButtonState
State of a sort direction button.
Definition window_gui.h:217
@ SBS_DOWN
Sort ascending.
Definition window_gui.h:219
@ SBS_OFF
Do not sort (with this button).
Definition window_gui.h:218
int WidgetID
Widget ID.
Definition window_type.h:20
EventState
State of handling an event.
@ ES_HANDLED
The passed event is handled.
@ ES_NOT_HANDLED
The passed event is not handled.
Functions related to zooming.
int ScaleByZoom(int value, ZoomLevel zoom)
Scale by zoom level, usually shift left (when zoom > ZOOM_LVL_MIN) When shifting right,...
Definition zoom_func.h:22
int ScaleSpriteTrad(int value)
Scale traditional pixel dimensions to GUI zoom level, for drawing sprites.
Definition zoom_func.h:107
ZoomLevel
All zoom levels we know.
Definition zoom_type.h:16
@ ZOOM_LVL_NORMAL
The normal zoom level.
Definition zoom_type.h:21