OpenTTD Source 15.0-beta2
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();
759 Rect inner = outer.Shrink(WidgetDimensions::scaled.bevel).Expand(1);
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 std::string text = GetStringForWidget(w, this);
2257 Dimension text_size = text.empty() ? Dimension{0, 0} : GetStringBoundingBox(text, this->text_size);
2258
2259 /* Account for the size of the frame's text if that exists */
2260 this->child->padding = WidgetDimensions::scaled.frametext;
2261 this->child->padding.top = std::max<uint8_t>(WidgetDimensions::scaled.frametext.top, text_size.height != 0 ? text_size.height + WidgetDimensions::scaled.frametext.top / 2 : 0);
2262
2263 this->smallest_x += this->child->padding.Horizontal();
2264 this->smallest_y += this->child->padding.Vertical();
2265
2266 this->smallest_x = std::max(this->smallest_x, text_size.width + WidgetDimensions::scaled.frametext.Horizontal());
2267 } else if (this->type == WWT_INSET) {
2268 /* Apply automatic padding for bevel thickness. */
2269 this->child->padding = WidgetDimensions::scaled.bevel;
2270
2271 this->smallest_x += this->child->padding.Horizontal();
2272 this->smallest_y += this->child->padding.Vertical();
2273 }
2274 this->ApplyAspectRatio();
2275 } else {
2276 Dimension d = {this->min_x, this->min_y};
2277 Dimension fill = {this->fill_x, this->fill_y};
2278 Dimension resize = {this->resize_x, this->resize_y};
2279 if (w != nullptr) { // A non-nullptr window pointer acts as switch to turn dynamic widget size on.
2280 if (this->type == WWT_FRAME || this->type == WWT_INSET) {
2281 std::string text = GetStringForWidget(w, this);
2282 Dimension background = GetStringBoundingBox(text, this->text_size);
2283 background.width += (this->type == WWT_FRAME) ? (WidgetDimensions::scaled.frametext.Horizontal()) : (WidgetDimensions::scaled.inset.Horizontal());
2284 d = maxdim(d, background);
2285 }
2286 if (this->index >= 0) {
2288 switch (this->type) {
2289 default: NOT_REACHED();
2293 }
2294 w->UpdateWidgetSize(this->index, d, padding, fill, resize);
2295 }
2296 }
2297 this->smallest_x = d.width;
2298 this->smallest_y = d.height;
2299 this->fill_x = fill.width;
2300 this->fill_y = fill.height;
2301 this->resize_x = resize.width;
2302 this->resize_y = resize.height;
2303 this->ApplyAspectRatio();
2304 }
2305}
2306
2307void NWidgetBackground::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
2308{
2309 this->StoreSizePosition(sizing, x, y, given_width, given_height);
2310
2311 if (this->child != nullptr) {
2312 uint x_offset = (rtl ? this->child->padding.right : this->child->padding.left);
2313 uint width = given_width - this->child->padding.Horizontal();
2314 uint height = given_height - this->child->padding.Vertical();
2315 this->child->AssignSizePosition(sizing, x + x_offset, y + this->child->padding.top, width, height, rtl);
2316 }
2317}
2318
2320{
2321 if (this->index >= 0) widget_lookup[this->index] = this;
2322 if (this->child != nullptr) this->child->FillWidgetLookup(widget_lookup);
2323}
2324
2326{
2327 if (this->current_x == 0 || this->current_y == 0) return;
2328
2329 Rect r = this->GetCurrentRect();
2330
2331 const DrawPixelInfo *dpi = _cur_dpi;
2332 if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2333
2334 switch (this->type) {
2335 case WWT_PANEL:
2336 DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, this->IsLowered() ? FrameFlag::Lowered : FrameFlags{});
2337 break;
2338
2339 case WWT_FRAME:
2340 DrawFrame(r, this->colour, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
2341 break;
2342
2343 case WWT_INSET:
2344 DrawInset(r, this->colour, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
2345 break;
2346
2347 default:
2348 NOT_REACHED();
2349 }
2350
2351 if (this->index >= 0) w->DrawWidget(r, this->index);
2352 if (this->child != nullptr) this->child->Draw(w);
2353
2354 if (this->IsDisabled()) {
2356 }
2357
2358 DrawOutline(w, this);
2359}
2360
2362{
2363 NWidgetCore *nwid = nullptr;
2364 if (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) {
2365 if (this->child != nullptr) nwid = this->child->GetWidgetFromPos(x, y);
2366 if (nwid == nullptr) nwid = this;
2367 }
2368 return nwid;
2369}
2370
2372{
2373 NWidgetBase *nwid = nullptr;
2374 if (this->child != nullptr) nwid = this->child->GetWidgetOfType(tp);
2375 if (nwid == nullptr && this->type == tp) nwid = this;
2376 return nwid;
2377}
2378
2379NWidgetViewport::NWidgetViewport(WidgetID index) : NWidgetCore(NWID_VIEWPORT, INVALID_COLOUR, index, 1, 1, {}, STR_NULL)
2380{
2381}
2382
2384{
2385 this->smallest_x = this->min_x;
2386 this->smallest_y = this->min_y;
2387 this->ApplyAspectRatio();
2388}
2389
2391{
2392 if (this->current_x == 0 || this->current_y == 0) return;
2393
2396 _transparency_opt &= (1 << TO_SIGNS) | (1 << TO_TEXT); // Disable all transparency, except textual stuff
2397 w->DrawViewport();
2398 _transparency_opt = to_backup;
2399 } else {
2400 w->DrawViewport();
2401 }
2402
2403 /* Optionally shade the viewport. */
2404 if (this->disp_flags.Any({NWidgetDisplayFlag::ShadeGrey, NWidgetDisplayFlag::ShadeDimmed})) {
2406 }
2407
2408 DrawOutline(w, this);
2409}
2410
2417void NWidgetViewport::InitializeViewport(Window *w, std::variant<TileIndex, VehicleID> focus, ZoomLevel zoom)
2418{
2419 InitializeWindowViewport(w, this->pos_x, this->pos_y, this->current_x, this->current_y, focus, zoom);
2420}
2421
2427{
2428 if (w->viewport == nullptr) return;
2429
2430 Viewport &vp = *w->viewport;
2431 vp.left = w->left + this->pos_x;
2432 vp.top = w->top + this->pos_y;
2433 vp.width = this->current_x;
2434 vp.height = this->current_y;
2435
2436 vp.virtual_width = ScaleByZoom(vp.width, vp.zoom);
2438}
2439
2449Scrollbar::size_type Scrollbar::GetScrolledRowFromWidget(int clickpos, const Window * const w, WidgetID widget, int padding, int line_height) const
2450{
2451 int pos = w->GetRowFromWidget(clickpos, widget, padding, line_height);
2452 if (pos != INT_MAX) pos += this->GetPosition();
2453 return (pos < 0 || pos >= this->GetCount()) ? Scrollbar::npos : pos;
2454}
2455
2470EventState Scrollbar::UpdateListPositionOnKeyPress(int &list_position, uint16_t keycode) const
2471{
2472 int new_pos = list_position;
2473 switch (keycode) {
2474 case WKC_UP:
2475 /* scroll up by one */
2476 new_pos--;
2477 break;
2478
2479 case WKC_DOWN:
2480 /* scroll down by one */
2481 new_pos++;
2482 break;
2483
2484 case WKC_PAGEUP:
2485 /* scroll up a page */
2486 new_pos -= this->GetCapacity();
2487 break;
2488
2489 case WKC_PAGEDOWN:
2490 /* scroll down a page */
2491 new_pos += this->GetCapacity();
2492 break;
2493
2494 case WKC_HOME:
2495 /* jump to beginning */
2496 new_pos = 0;
2497 break;
2498
2499 case WKC_END:
2500 /* jump to end */
2501 new_pos = this->GetCount() - 1;
2502 break;
2503
2504 default:
2505 return ES_NOT_HANDLED;
2506 }
2507
2508 /* If there are no elements, there is nothing to scroll/update. */
2509 if (this->GetCount() != 0) {
2510 list_position = Clamp(new_pos, 0, this->GetCount() - 1);
2511 }
2512 return ES_HANDLED;
2513}
2514
2515
2524{
2525 NWidgetBase *nwid = w->GetWidget<NWidgetBase>(widget);
2526 if (this->IsVertical()) {
2527 this->SetCapacity(((int)nwid->current_y - padding) / (int)nwid->resize_y);
2528 } else {
2529 this->SetCapacity(((int)nwid->current_x - padding) / (int)nwid->resize_x);
2530 }
2531}
2532
2540Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
2541{
2542 const int count = sb.GetCount() * resize_step;
2543 const int position = sb.GetPosition() * resize_step;
2544
2545 if (sb.IsVertical()) {
2546 r.top -= position;
2547 r.bottom = r.top + count;
2548 } else {
2549 bool rtl = _current_text_dir == TD_RTL;
2550 if (rtl) {
2551 r.right += position;
2552 r.left = r.right - count;
2553 } else {
2554 r.left -= position;
2555 r.right = r.left + count;
2556 }
2557 }
2558
2559 return r;
2560}
2561
2568NWidgetScrollbar::NWidgetScrollbar(WidgetType tp, Colours colour, WidgetID index) : NWidgetCore(tp, colour, index, 1, 1, {}, STR_NULL), Scrollbar(tp != NWID_HSCROLLBAR)
2569{
2570 assert(tp == NWID_HSCROLLBAR || tp == NWID_VSCROLLBAR);
2571
2572 switch (this->type) {
2573 case NWID_HSCROLLBAR:
2574 this->SetResize(1, 0);
2575 this->SetFill(1, 0);
2576 this->SetToolTip(STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST);
2577 break;
2578
2579 case NWID_VSCROLLBAR:
2580 this->SetResize(0, 1);
2581 this->SetFill(0, 1);
2582 this->SetToolTip(STR_TOOLTIP_VSCROLL_BAR_SCROLLS_LIST);
2583 break;
2584
2585 default: NOT_REACHED();
2586 }
2587}
2588
2590{
2591 this->min_x = 0;
2592 this->min_y = 0;
2593
2594 switch (this->type) {
2595 case NWID_HSCROLLBAR:
2596 this->SetMinimalSizeAbsolute(NWidgetScrollbar::GetHorizontalDimension().width * 3, NWidgetScrollbar::GetHorizontalDimension().height);
2597 break;
2598
2599 case NWID_VSCROLLBAR:
2600 this->SetMinimalSizeAbsolute(NWidgetScrollbar::GetVerticalDimension().width, NWidgetScrollbar::GetVerticalDimension().height * 3);
2601 break;
2602
2603 default: NOT_REACHED();
2604 }
2605
2606 this->smallest_x = this->min_x;
2607 this->smallest_y = this->min_y;
2608}
2609
2611{
2612 if (this->current_x == 0 || this->current_y == 0) return;
2613
2614 Rect r = this->GetCurrentRect();
2615
2616 const DrawPixelInfo *dpi = _cur_dpi;
2617 if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2618
2619 bool up_lowered = this->disp_flags.Test(NWidgetDisplayFlag::ScrollbarUp);
2620 bool down_lowered = this->disp_flags.Test(NWidgetDisplayFlag::ScrollbarDown);
2621 bool middle_lowered = !this->disp_flags.Any({NWidgetDisplayFlag::ScrollbarUp, NWidgetDisplayFlag::ScrollbarDown}) && w->mouse_capture_widget == this->index;
2622
2623 if (this->type == NWID_HSCROLLBAR) {
2624 DrawHorizontalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2625 } else {
2626 DrawVerticalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2627 }
2628
2629 if (this->IsDisabled()) {
2631 }
2632
2633 DrawOutline(w, this);
2634}
2635
2636/* static */ void NWidgetScrollbar::InvalidateDimensionCache()
2637{
2638 vertical_dimension.width = vertical_dimension.height = 0;
2639 horizontal_dimension.width = horizontal_dimension.height = 0;
2640}
2641
2642/* static */ Dimension NWidgetScrollbar::GetVerticalDimension()
2643{
2644 if (vertical_dimension.width == 0) {
2645 vertical_dimension = maxdim(GetScaledSpriteSize(SPR_ARROW_UP), GetScaledSpriteSize(SPR_ARROW_DOWN));
2648 }
2649 return vertical_dimension;
2650}
2651
2652/* static */ Dimension NWidgetScrollbar::GetHorizontalDimension()
2653{
2654 if (horizontal_dimension.width == 0) {
2655 horizontal_dimension = maxdim(GetScaledSpriteSize(SPR_ARROW_LEFT), GetScaledSpriteSize(SPR_ARROW_RIGHT));
2658 }
2659 return horizontal_dimension;
2660}
2661
2664
2667{
2668 shadebox_dimension.width = shadebox_dimension.height = 0;
2669 debugbox_dimension.width = debugbox_dimension.height = 0;
2670 defsizebox_dimension.width = defsizebox_dimension.height = 0;
2671 stickybox_dimension.width = stickybox_dimension.height = 0;
2672 resizebox_dimension.width = resizebox_dimension.height = 0;
2673 closebox_dimension.width = closebox_dimension.height = 0;
2674 dropdown_dimension.width = dropdown_dimension.height = 0;
2675}
2676
2684
2693NWidgetLeaf::NWidgetLeaf(WidgetType tp, Colours colour, WidgetID index, const WidgetData &data, StringID tip) : NWidgetCore(tp, colour, index, 1, 1, data, tip)
2694{
2695 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);
2696 this->min_x = 0;
2697 this->min_y = 0;
2698 this->SetResize(0, 0);
2699
2700 switch (tp) {
2701 case WWT_EMPTY:
2702 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_EMPTY should not have a colour");
2703 break;
2704
2705 case WWT_TEXT:
2706 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_TEXT should not have a colour");
2707 this->SetFill(0, 0);
2709 break;
2710
2711 case WWT_LABEL:
2712 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_LABEL should not have a colour");
2713 [[fallthrough]];
2714
2715 case WWT_PUSHBTN:
2716 case WWT_IMGBTN:
2717 case WWT_PUSHIMGBTN:
2718 case WWT_IMGBTN_2:
2719 case WWT_TEXTBTN:
2720 case WWT_PUSHTXTBTN:
2721 case WWT_TEXTBTN_2:
2722 case WWT_MATRIX:
2724 case NWID_PUSHBUTTON_DROPDOWN:
2725 this->SetFill(0, 0);
2726 break;
2727
2728 case WWT_ARROWBTN:
2729 case WWT_PUSHARROWBTN:
2730 this->SetFill(0, 0);
2731 this->SetAspect(WidgetDimensions::ASPECT_LEFT_RIGHT_BUTTON);
2732 break;
2733
2734 case WWT_EDITBOX:
2735 this->SetFill(0, 0);
2736 break;
2737
2738 case WWT_CAPTION:
2739 this->SetFill(1, 0);
2740 this->SetResize(1, 0);
2742 this->SetMinimalTextLines(1, WidgetDimensions::unscaled.captiontext.Vertical(), FS_NORMAL);
2743 this->SetToolTip(STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
2744 break;
2745
2746 case WWT_STICKYBOX:
2747 this->SetFill(0, 0);
2749 this->SetToolTip(STR_TOOLTIP_STICKY);
2750 this->SetAspect(this->min_x, this->min_y);
2751 break;
2752
2753 case WWT_SHADEBOX:
2754 this->SetFill(0, 0);
2756 this->SetToolTip(STR_TOOLTIP_SHADE);
2757 this->SetAspect(this->min_x, this->min_y);
2758 break;
2759
2760 case WWT_DEBUGBOX:
2761 this->SetFill(0, 0);
2763 this->SetToolTip(STR_TOOLTIP_DEBUG);
2764 this->SetAspect(this->min_x, this->min_y);
2765 break;
2766
2767 case WWT_DEFSIZEBOX:
2768 this->SetFill(0, 0);
2770 this->SetToolTip(STR_TOOLTIP_DEFSIZE);
2771 this->SetAspect(this->min_x, this->min_y);
2772 break;
2773
2774 case WWT_RESIZEBOX:
2775 this->SetFill(0, 0);
2778 this->SetToolTip(STR_TOOLTIP_RESIZE);
2779 break;
2780
2781 case WWT_CLOSEBOX:
2782 this->SetFill(0, 0);
2784 this->SetToolTip(STR_TOOLTIP_CLOSE_WINDOW);
2785 this->SetAspect(this->min_x, this->min_y);
2786 break;
2787
2788 case WWT_DROPDOWN:
2789 this->SetFill(0, 0);
2791 this->SetAlignment(SA_TOP | SA_LEFT);
2792 break;
2793
2794 default:
2795 NOT_REACHED();
2796 }
2797}
2798
2800{
2801 Dimension padding = {0, 0};
2802 Dimension size = {this->min_x, this->min_y};
2803 Dimension fill = {this->fill_x, this->fill_y};
2804 Dimension resize = {this->resize_x, this->resize_y};
2805 switch (this->type) {
2806 case WWT_EMPTY: {
2807 break;
2808 }
2809 case WWT_MATRIX: {
2811 break;
2812 }
2813 case WWT_SHADEBOX: {
2815 if (NWidgetLeaf::shadebox_dimension.width == 0) {
2816 NWidgetLeaf::shadebox_dimension = maxdim(GetScaledSpriteSize(SPR_WINDOW_SHADE), GetScaledSpriteSize(SPR_WINDOW_UNSHADE));
2819 }
2821 break;
2822 }
2823 case WWT_DEBUGBOX:
2826 if (NWidgetLeaf::debugbox_dimension.width == 0) {
2830 }
2832 } else {
2833 /* If the setting is disabled we don't want to see it! */
2834 size.width = 0;
2835 fill.width = 0;
2836 resize.width = 0;
2837 }
2838 break;
2839
2840 case WWT_STICKYBOX: {
2842 if (NWidgetLeaf::stickybox_dimension.width == 0) {
2846 }
2848 break;
2849 }
2850
2851 case WWT_DEFSIZEBOX: {
2853 if (NWidgetLeaf::defsizebox_dimension.width == 0) {
2857 }
2859 break;
2860 }
2861
2862 case WWT_RESIZEBOX: {
2864 if (NWidgetLeaf::resizebox_dimension.width == 0) {
2865 NWidgetLeaf::resizebox_dimension = maxdim(GetScaledSpriteSize(SPR_WINDOW_RESIZE_LEFT), GetScaledSpriteSize(SPR_WINDOW_RESIZE_RIGHT));
2868 }
2870 break;
2871 }
2872 case WWT_EDITBOX: {
2873 Dimension sprite_size = GetScaledSpriteSize(_current_text_dir == TD_RTL ? SPR_IMG_DELETE_RIGHT : SPR_IMG_DELETE_LEFT);
2874 size.width = std::max(size.width, ScaleGUITrad(30) + sprite_size.width);
2875 size.height = std::max(sprite_size.height, GetStringBoundingBox("_").height + WidgetDimensions::scaled.framerect.Vertical());
2876 }
2877 [[fallthrough]];
2878 case WWT_PUSHBTN: {
2880 break;
2881 }
2882 case WWT_IMGBTN:
2883 case WWT_IMGBTN_2:
2884 case WWT_PUSHIMGBTN: {
2886 Dimension d2 = GetScaledSpriteSize(this->widget_data.sprite);
2887 if (this->type == WWT_IMGBTN_2) d2 = maxdim(d2, GetScaledSpriteSize(this->widget_data.sprite + 1));
2888 d2.width += padding.width;
2889 d2.height += padding.height;
2890 size = maxdim(size, d2);
2891 break;
2892 }
2893 case WWT_ARROWBTN:
2894 case WWT_PUSHARROWBTN: {
2896 Dimension d2 = maxdim(GetScaledSpriteSize(SPR_ARROW_LEFT), GetScaledSpriteSize(SPR_ARROW_RIGHT));
2897 d2.width += padding.width;
2898 d2.height += padding.height;
2899 size = maxdim(size, d2);
2900 break;
2901 }
2902
2903 case WWT_CLOSEBOX: {
2905 if (NWidgetLeaf::closebox_dimension.width == 0) {
2909 }
2911 break;
2912 }
2913 case WWT_TEXTBTN:
2914 case WWT_PUSHTXTBTN:
2915 case WWT_TEXTBTN_2: {
2917 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2918 d2.width += padding.width;
2919 d2.height += padding.height;
2920 size = maxdim(size, d2);
2921 break;
2922 }
2923 case WWT_LABEL:
2924 case WWT_TEXT: {
2925 size = maxdim(size, GetStringBoundingBox(GetStringForWidget(w, this), this->text_size));
2926 break;
2927 }
2928 case WWT_CAPTION: {
2930 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2931 d2.width += padding.width;
2932 d2.height += padding.height;
2933 size = maxdim(size, d2);
2934 break;
2935 }
2936 case WWT_DROPDOWN:
2938 case NWID_PUSHBUTTON_DROPDOWN: {
2939 if (NWidgetLeaf::dropdown_dimension.width == 0) {
2943 }
2945 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2946 d2.width += padding.width;
2947 d2.height = std::max(d2.height + padding.height, NWidgetLeaf::dropdown_dimension.height);
2948 size = maxdim(size, d2);
2949 break;
2950 }
2951 default:
2952 NOT_REACHED();
2953 }
2954
2955 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
2956
2957 this->smallest_x = size.width;
2958 this->smallest_y = size.height;
2959 this->fill_x = fill.width;
2960 this->fill_y = fill.height;
2961 this->resize_x = resize.width;
2962 this->resize_y = resize.height;
2963 this->ApplyAspectRatio();
2964}
2965
2967{
2968 if (this->current_x == 0 || this->current_y == 0) return;
2969
2970 /* Setup a clipping rectangle... for WWT_EMPTY or WWT_TEXT, an extra scaled pixel is allowed in case text shadow encroaches. */
2971 int extra = (this->type == WWT_EMPTY || this->type == WWT_TEXT) ? ScaleGUITrad(1) : 0;
2972 DrawPixelInfo new_dpi;
2973 if (!FillDrawPixelInfo(&new_dpi, this->pos_x, this->pos_y, this->current_x + extra, this->current_y + extra)) return;
2974 /* ...but keep coordinates relative to the window. */
2975 new_dpi.left += this->pos_x;
2976 new_dpi.top += this->pos_y;
2977
2978 AutoRestoreBackup dpi_backup(_cur_dpi, &new_dpi);
2979
2980 Rect r = this->GetCurrentRect();
2981
2982 bool clicked = this->IsLowered();
2983 switch (this->type) {
2984 case WWT_EMPTY:
2985 /* WWT_EMPTY used as a spacer indicates a potential design issue. */
2986 if (this->index == -1 && _draw_widget_outlines) {
2988 }
2989 break;
2990
2991 case WWT_PUSHBTN:
2992 DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
2993 break;
2994
2995 case WWT_IMGBTN:
2996 case WWT_PUSHIMGBTN:
2997 case WWT_IMGBTN_2:
2998 DrawImageButtons(r, this->type, this->colour, clicked, this->widget_data.sprite, this->align);
2999 break;
3000
3001 case WWT_TEXTBTN:
3002 case WWT_PUSHTXTBTN:
3003 case WWT_TEXTBTN_2:
3004 DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
3005 DrawLabel(r, this->text_colour, GetStringForWidget(w, this, (type & WWT_MASK) == WWT_TEXTBTN_2 && clicked), this->align, this->text_size);
3006 break;
3007
3008 case WWT_ARROWBTN:
3009 case WWT_PUSHARROWBTN: {
3010 SpriteID sprite;
3011 switch (this->widget_data.arrow_widget_type) {
3012 case AWV_DECREASE: sprite = _current_text_dir != TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
3013 case AWV_INCREASE: sprite = _current_text_dir == TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
3014 case AWV_LEFT: sprite = SPR_ARROW_LEFT; break;
3015 case AWV_RIGHT: sprite = SPR_ARROW_RIGHT; break;
3016 default: NOT_REACHED();
3017 }
3018 DrawImageButtons(r, WWT_PUSHIMGBTN, this->colour, clicked, sprite, this->align);
3019 break;
3020 }
3021
3022 case WWT_LABEL:
3023 DrawLabel(r, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3024 break;
3025
3026 case WWT_TEXT:
3027 DrawText(r, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3028 break;
3029
3030 case WWT_MATRIX:
3031 DrawMatrix(r, this->colour, clicked, this->widget_data.matrix.width, this->widget_data.matrix.height, this->resize_x, this->resize_y);
3032 break;
3033
3034 case WWT_EDITBOX: {
3035 const QueryString *query = w->GetQueryString(this->index);
3036 if (query != nullptr) query->DrawEditBox(w, this->index);
3037 break;
3038 }
3039
3040 case WWT_CAPTION:
3041 DrawCaption(r, this->colour, w->owner, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3042 break;
3043
3044 case WWT_SHADEBOX:
3045 DrawShadeBox(r, this->colour, w->IsShaded());
3046 break;
3047
3048 case WWT_DEBUGBOX:
3049 DrawDebugBox(r, this->colour, clicked);
3050 break;
3051
3052 case WWT_STICKYBOX:
3054 break;
3055
3056 case WWT_DEFSIZEBOX:
3057 DrawDefSizeBox(r, this->colour, clicked);
3058 break;
3059
3060 case WWT_RESIZEBOX:
3061 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);
3062 break;
3063
3064 case WWT_CLOSEBOX:
3065 DrawCloseBox(r, this->colour);
3066 break;
3067
3068 case WWT_DROPDOWN:
3069 DrawButtonDropdown(r, this->colour, false, clicked, GetStringForWidget(w, this), this->align);
3070 break;
3071
3073 case NWID_PUSHBUTTON_DROPDOWN:
3074 DrawButtonDropdown(r, this->colour, clicked, this->disp_flags.Test(NWidgetDisplayFlag::DropdownActive), GetStringForWidget(w, this), this->align);
3075 break;
3076
3077 default:
3078 NOT_REACHED();
3079 }
3080 if (this->index >= 0) w->DrawWidget(r, this->index);
3081
3082 if (this->IsDisabled()) {
3084 }
3085
3086 DrawOutline(w, this);
3087}
3088
3097{
3098 if (_current_text_dir == TD_LTR) {
3099 int button_width = this->pos_x + this->current_x - NWidgetLeaf::dropdown_dimension.width;
3100 return pt.x < button_width;
3101 } else {
3102 int button_left = this->pos_x + NWidgetLeaf::dropdown_dimension.width;
3103 return pt.x >= button_left;
3104 }
3105}
3106
3107/* == Conversion code from NWidgetPart array to NWidgetBase* tree == */
3108
3115{
3116 return tp > WPT_ATTRIBUTE_BEGIN && tp < WPT_ATTRIBUTE_END;
3117}
3118
3126{
3127 switch (nwid.type) {
3128 case WPT_RESIZE: {
3129 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3130 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_RESIZE requires NWidgetResizeBase");
3131 assert(nwid.u.xy.x >= 0 && nwid.u.xy.y >= 0);
3132 nwrb->SetResize(nwid.u.xy.x, nwid.u.xy.y);
3133 break;
3134 }
3135
3136 case WPT_MINSIZE: {
3137 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3138 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_MINSIZE requires NWidgetResizeBase");
3139 assert(nwid.u.xy.x >= 0 && nwid.u.xy.y >= 0);
3140 nwrb->SetMinimalSize(nwid.u.xy.x, nwid.u.xy.y);
3141 break;
3142 }
3143
3144 case WPT_MINTEXTLINES: {
3145 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3146 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_MINTEXTLINES requires NWidgetResizeBase");
3147 assert(nwid.u.text_lines.size >= FS_BEGIN && nwid.u.text_lines.size < FS_END);
3149 break;
3150 }
3151
3152 case WPT_TEXTSTYLE: {
3153 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3154 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_TEXTSTYLE requires NWidgetCore");
3155 nwc->SetTextStyle(nwid.u.text_style.colour, nwid.u.text_style.size);
3156 break;
3157 }
3158
3159 case WPT_ALIGNMENT: {
3160 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3161 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_ALIGNMENT requires NWidgetCore");
3162 nwc->SetAlignment(nwid.u.align.align);
3163 break;
3164 }
3165
3166 case WPT_FILL: {
3167 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3168 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_FILL requires NWidgetResizeBase");
3169 nwrb->SetFill(nwid.u.xy.x, nwid.u.xy.y);
3170 break;
3171 }
3172
3173 case WPT_DATATIP: {
3174 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3175 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_DATATIP requires NWidgetCore");
3176 nwc->widget_data = nwid.u.data_tip.data;
3177 nwc->SetToolTip(nwid.u.data_tip.tooltip);
3178 break;
3179 }
3180
3181 case WPT_PADDING:
3182 if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_PADDING requires NWidgetBase");
3183 dest->SetPadding(nwid.u.padding);
3184 break;
3185
3186 case WPT_PIPSPACE: {
3187 NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(dest);
3188 if (nwc != nullptr) nwc->SetPIP(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3189
3190 NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(dest);
3191 if (nwb != nullptr) nwb->SetPIP(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3192
3193 if (nwc == nullptr && nwb == nullptr) [[unlikely]] throw std::runtime_error("WPT_PIPSPACE requires NWidgetPIPContainer or NWidgetBackground");
3194 break;
3195 }
3196
3197 case WPT_PIPRATIO: {
3198 NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(dest);
3199 if (nwc != nullptr) nwc->SetPIPRatio(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3200
3201 NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(dest);
3202 if (nwb != nullptr) nwb->SetPIPRatio(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3203
3204 if (nwc == nullptr && nwb == nullptr) [[unlikely]] throw std::runtime_error("WPT_PIPRATIO requires NWidgetPIPContainer or NWidgetBackground");
3205 break;
3206 }
3207
3208 case WPT_SCROLLBAR: {
3209 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3210 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_SCROLLBAR requires NWidgetCore");
3211 nwc->scrollbar_index = nwid.u.widget.index;
3212 break;
3213 }
3214
3215 case WPT_ASPECT: {
3216 if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_ASPECT requires NWidgetBase");
3217 dest->aspect_ratio = nwid.u.aspect.ratio;
3218 dest->aspect_flags = nwid.u.aspect.flags;
3219 break;
3220 }
3221
3222 default:
3223 NOT_REACHED();
3224 }
3225}
3226
3233static std::unique_ptr<NWidgetBase> MakeNWidget(const NWidgetPart &nwid)
3234{
3235 assert(!IsAttributeWidgetPartType(nwid.type));
3236 assert(nwid.type != WPT_ENDCONTAINER);
3237
3238 switch (nwid.type) {
3239 case NWID_SPACER: return std::make_unique<NWidgetSpacer>(0, 0);
3240
3241 case WWT_PANEL: [[fallthrough]];
3242 case WWT_INSET: [[fallthrough]];
3243 case WWT_FRAME: return std::make_unique<NWidgetBackground>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index);
3244
3245 case NWID_HORIZONTAL: return std::make_unique<NWidgetHorizontal>(nwid.u.cont_flags);
3246 case NWID_HORIZONTAL_LTR: return std::make_unique<NWidgetHorizontalLTR>(nwid.u.cont_flags);
3247 case NWID_VERTICAL: return std::make_unique<NWidgetVertical>(nwid.u.cont_flags);
3248 case NWID_SELECTION: return std::make_unique<NWidgetStacked>(nwid.u.widget.index);
3249 case NWID_MATRIX: return std::make_unique<NWidgetMatrix>(nwid.u.widget.colour, nwid.u.widget.index);
3250 case NWID_VIEWPORT: return std::make_unique<NWidgetViewport>(nwid.u.widget.index);
3251 case NWID_LAYER: return std::make_unique<NWidgetLayer>(nwid.u.widget.index);
3252
3253 case NWID_HSCROLLBAR: [[fallthrough]];
3254 case NWID_VSCROLLBAR: return std::make_unique<NWidgetScrollbar>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index);
3255
3256 case WPT_FUNCTION: return nwid.u.func_ptr();
3257
3258 default:
3259 assert((nwid.type & WWT_MASK) < WWT_LAST || (nwid.type & WWT_MASK) == NWID_BUTTON_DROPDOWN);
3260 return std::make_unique<NWidgetLeaf>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index, WidgetData{}, STR_NULL);
3261 }
3262}
3263
3277static 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)
3278{
3279 dest = nullptr;
3280
3281 if (IsAttributeWidgetPartType(nwid_begin->type)) [[unlikely]] throw std::runtime_error("Expected non-attribute NWidgetPart type");
3282 if (nwid_begin->type == WPT_ENDCONTAINER) return nwid_begin;
3283
3284 fill_dest = IsContainerWidgetType(nwid_begin->type);
3285 dest = MakeNWidget(*nwid_begin);
3286 if (dest == nullptr) return nwid_begin;
3287
3288 ++nwid_begin;
3289
3290 /* Once a widget is created, we're now looking for attributes. */
3291 while (nwid_begin != nwid_end && IsAttributeWidgetPartType(nwid_begin->type)) {
3292 ApplyNWidgetPartAttribute(*nwid_begin, dest.get());
3293 ++nwid_begin;
3294 }
3295
3296 return nwid_begin;
3297}
3298
3305{
3306 return tp == NWID_HORIZONTAL || tp == NWID_HORIZONTAL_LTR || tp == NWID_VERTICAL || tp == NWID_MATRIX
3307 || tp == WWT_PANEL || tp == WWT_FRAME || tp == WWT_INSET || tp == NWID_SELECTION || tp == NWID_LAYER;
3308}
3309
3317static 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)
3318{
3319 /* If *parent == nullptr, only the first widget is read and returned. Otherwise, *parent must point to either
3320 * a #NWidgetContainer or a #NWidgetBackground object, and parts are added as much as possible. */
3321 NWidgetContainer *nwid_cont = dynamic_cast<NWidgetContainer *>(parent.get());
3322 NWidgetBackground *nwid_parent = dynamic_cast<NWidgetBackground *>(parent.get());
3323 assert(parent == nullptr || (nwid_cont != nullptr && nwid_parent == nullptr) || (nwid_cont == nullptr && nwid_parent != nullptr));
3324
3325 while (nwid_begin != nwid_end) {
3326 std::unique_ptr<NWidgetBase> sub_widget = nullptr;
3327 bool fill_sub = false;
3328 nwid_begin = MakeNWidget(nwid_begin, nwid_end, sub_widget, fill_sub);
3329
3330 /* Break out of loop when end reached */
3331 if (sub_widget == nullptr) break;
3332
3333 /* If sub-widget is a container, recursively fill that container. */
3334 if (fill_sub && IsContainerWidgetType(sub_widget->type)) {
3335 nwid_begin = MakeWidgetTree(nwid_begin, nwid_end, sub_widget);
3336 }
3337
3338 /* Add sub_widget to parent container if available, otherwise return the widget to the caller. */
3339 if (nwid_cont != nullptr) nwid_cont->Add(std::move(sub_widget));
3340 if (nwid_parent != nullptr) nwid_parent->Add(std::move(sub_widget));
3341 if (nwid_cont == nullptr && nwid_parent == nullptr) {
3342 parent = std::move(sub_widget);
3343 return nwid_begin;
3344 }
3345 }
3346
3347 if (nwid_begin == nwid_end) return nwid_begin; // Reached the end of the array of parts?
3348
3349 assert(nwid_begin < nwid_end);
3350 assert(nwid_begin->type == WPT_ENDCONTAINER);
3351 return std::next(nwid_begin); // *nwid_begin is also 'used'
3352}
3353
3361std::unique_ptr<NWidgetBase> MakeNWidgets(std::span<const NWidgetPart> nwid_parts, std::unique_ptr<NWidgetBase> &&container)
3362{
3363 if (container == nullptr) container = std::make_unique<NWidgetVertical>();
3364 [[maybe_unused]] auto nwid_part = MakeWidgetTree(std::begin(nwid_parts), std::end(nwid_parts), container);
3365#ifdef WITH_ASSERT
3366 if (nwid_part != std::end(nwid_parts)) [[unlikely]] throw std::runtime_error("Did not consume all NWidgetParts");
3367#endif
3368 return std::move(container);
3369}
3370
3380std::unique_ptr<NWidgetBase> MakeWindowNWidgetTree(std::span<const NWidgetPart> nwid_parts, NWidgetStacked **shade_select)
3381{
3382 auto nwid_begin = std::begin(nwid_parts);
3383 auto nwid_end = std::end(nwid_parts);
3384
3385 *shade_select = nullptr;
3386
3387 /* Read the first widget recursively from the array. */
3388 std::unique_ptr<NWidgetBase> nwid = nullptr;
3389 nwid_begin = MakeWidgetTree(nwid_begin, nwid_end, nwid);
3390 assert(nwid != nullptr);
3391
3392 NWidgetHorizontal *hor_cont = dynamic_cast<NWidgetHorizontal *>(nwid.get());
3393
3394 auto root = std::make_unique<NWidgetVertical>();
3395 root->Add(std::move(nwid));
3396 if (nwid_begin == nwid_end) return root; // There is no body at all.
3397
3398 if (hor_cont != nullptr && hor_cont->GetWidgetOfType(WWT_CAPTION) != nullptr && hor_cont->GetWidgetOfType(WWT_SHADEBOX) != nullptr) {
3399 /* If the first widget has a title bar and a shade box, silently add a shade selection widget in the tree. */
3400 auto shade_stack = std::make_unique<NWidgetStacked>(-1);
3401 *shade_select = shade_stack.get();
3402 /* Load the remaining parts into the shade stack. */
3403 shade_stack->Add(MakeNWidgets({nwid_begin, nwid_end}, std::make_unique<NWidgetVertical>()));
3404 root->Add(std::move(shade_stack));
3405 return root;
3406 }
3407
3408 /* Load the remaining parts into 'root'. */
3409 return MakeNWidgets({nwid_begin, nwid_end}, std::move(root));
3410}
3411
3422std::unique_ptr<NWidgetBase> MakeCompanyButtonRows(WidgetID widget_first, WidgetID widget_last, Colours button_colour, int max_length, StringID button_tooltip, bool resizable)
3423{
3424 assert(max_length >= 1);
3425 std::unique_ptr<NWidgetVertical> vert = nullptr; // Storage for all rows.
3426 std::unique_ptr<NWidgetHorizontal> hor = nullptr; // Storage for buttons in one row.
3427 int hor_length = 0;
3428
3429 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON, nullptr, ZOOM_LVL_NORMAL);
3430 sprite_size.width += WidgetDimensions::unscaled.matrix.Horizontal();
3431 sprite_size.height += WidgetDimensions::unscaled.matrix.Vertical();
3432
3433 for (WidgetID widnum = widget_first; widnum <= widget_last; widnum++) {
3434 /* Ensure there is room in 'hor' for another button. */
3435 if (hor_length == max_length) {
3436 if (vert == nullptr) vert = std::make_unique<NWidgetVertical>();
3437 vert->Add(std::move(hor));
3438 hor = nullptr;
3439 hor_length = 0;
3440 }
3441 if (hor == nullptr) {
3442 hor = std::make_unique<NWidgetHorizontal>();
3443 hor_length = 0;
3444 }
3445
3446 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, button_colour, widnum);
3447 panel->SetMinimalSize(sprite_size.width, sprite_size.height);
3448 panel->SetFill(1, 1);
3449 if (resizable) panel->SetResize(1, 0);
3450 panel->SetToolTip(button_tooltip);
3451 hor->Add(std::move(panel));
3452 hor_length++;
3453 }
3454 if (vert == nullptr) return hor; // All buttons fit in a single row.
3455
3456 if (hor_length > 0 && hor_length < max_length) {
3457 /* Last row is partial, add a spacer at the end to force all buttons to the left. */
3458 auto spc = std::make_unique<NWidgetSpacer>(sprite_size.width, sprite_size.height);
3459 spc->SetFill(1, 1);
3460 if (resizable) spc->SetResize(1, 0);
3461 hor->Add(std::move(spc));
3462 }
3463 if (hor != nullptr) vert->Add(std::move(hor));
3464 return vert;
3465}
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:2361
NWidgetBase * GetWidgetOfType(WidgetType tp) override
Retrieve a widget by its type.
Definition widget.cpp:2371
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:2307
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:2325
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:2319
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:2966
static void InvalidateDimensionCache()
Reset the cached dimensions.
Definition widget.cpp:2666
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:2693
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:2799
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:3096
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:2610
NWidgetScrollbar(WidgetType tp, Colours colour, WidgetID index)
Scrollbar widget.
Definition widget.cpp:2568
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2589
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:2426
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2390
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2383
void InitializeViewport(Window *w, std::variant< TileIndex, VehicleID > focus, ZoomLevel zoom)
Initialize the viewport of the window.
Definition widget.cpp:2417
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:2449
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:2523
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:2470
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:244
@ FS_BEGIN
First font.
Definition gfx_type.h:251
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:245
StringAlignment
How to align the to-be drawn text.
Definition gfx_type.h:376
@ SA_TOP
Top align the text.
Definition gfx_type.h:382
@ SA_LEFT
Left align the text.
Definition gfx_type.h:377
@ SA_HOR_MASK
Mask for horizontal alignment.
Definition gfx_type.h:380
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:379
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:378
@ SA_VERT_MASK
Mask for vertical alignment.
Definition gfx_type.h:385
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition gfx_type.h:389
@ SA_BOTTOM
Bottom align the text.
Definition gfx_type.h:384
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:387
@ SA_VERT_CENTER
Vertically center the text.
Definition gfx_type.h:383
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:296
@ FILLRECT_CHECKER
Draw only every second pixel, used for greying-out.
Definition gfx_type.h:335
@ FILLRECT_RECOLOUR
Apply a recolour sprite to the screen content.
Definition gfx_type.h:336
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:3380
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:3361
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:945
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:58
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:424
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:57
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:126
Dimensions (a width and height) of a rectangle in 2D.
Data about how and where to blit pixels.
Definition gfx_type.h:158
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
std::unique_ptr< ViewportData > viewport
Pointer to viewport data, if present.
Definition window_gui.h:319
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:504
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
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:333
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:212
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:218
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:2540
void ApplyNWidgetPartAttribute(const NWidgetPart &nwid, NWidgetBase *dest)
Apply an attribute NWidgetPart to an NWidget.
Definition widget.cpp:3125
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:3304
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:3233
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:3114
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:3317
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:3422
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:77
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