OpenTTD Source 20250716-master-g6b6caa6fa8
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 "settings_gui.h"
14#include "strings_type.h"
15#include "window_gui.h"
16#include "viewport_func.h"
17#include "zoom_func.h"
18#include "strings_func.h"
19#include "transparency.h"
21#include "settings_type.h"
22#include "querystring_gui.h"
23
24#include "table/sprites.h"
25#include "table/strings.h"
27
28#include "safeguards.h"
29
31
32static std::string GetStringForWidget(const Window *w, const NWidgetCore *nwid, bool secondary = false)
33{
34 StringID stringid = nwid->GetString();
35 if (nwid->GetIndex() < 0) {
36 if (stringid == STR_NULL) return {};
37
38 return GetString(stringid + (secondary ? 1 : 0));
39 }
40
41 return w->GetWidgetString(nwid->GetIndex(), stringid + (secondary ? 1 : 0));
42}
43
49static inline RectPadding ScaleGUITrad(const RectPadding &r)
50{
51 return {(uint8_t)ScaleGUITrad(r.left), (uint8_t)ScaleGUITrad(r.top), (uint8_t)ScaleGUITrad(r.right), (uint8_t)ScaleGUITrad(r.bottom)};
52}
53
59static inline Dimension ScaleGUITrad(const Dimension &dim)
60{
61 return {(uint)ScaleGUITrad(dim.width), (uint)ScaleGUITrad(dim.height)};
62}
63
69{
70 Point offset;
71 Dimension d = GetSpriteSize(sprid, &offset, ZoomLevel::Normal);
72 d.width -= offset.x;
73 d.height -= offset.y;
74 return ScaleGUITrad(d);
75}
76
81{
88 } else {
90 }
105
111}
112
120static inline Point GetAlignedPosition(const Rect &r, const Dimension &d, StringAlignment align)
121{
122 Point p;
123 /* In case we have a RTL language we swap the alignment. */
124 if (!(align & SA_FORCE) && _current_text_dir == TD_RTL && (align & SA_HOR_MASK) != SA_HOR_CENTER) align ^= SA_RIGHT;
125 switch (align & SA_HOR_MASK) {
126 case SA_LEFT: p.x = r.left; break;
127 case SA_HOR_CENTER: p.x = CentreBounds(r.left, r.right, d.width); break;
128 case SA_RIGHT: p.x = r.right + 1 - d.width; break;
129 default: NOT_REACHED();
130 }
131 switch (align & SA_VERT_MASK) {
132 case SA_TOP: p.y = r.top; break;
133 case SA_VERT_CENTER: p.y = CentreBounds(r.top, r.bottom, d.height); break;
134 case SA_BOTTOM: p.y = r.bottom + 1 - d.height; break;
135 default: NOT_REACHED();
136 }
137 return p;
138}
139
149static Point HandleScrollbarHittest(const Scrollbar *sb, int top, int bottom, bool horizontal)
150{
151 /* Base for reversion */
152 int rev_base = top + bottom;
153 int button_size;
154 if (horizontal) {
155 button_size = NWidgetScrollbar::GetHorizontalDimension().width;
156 } else {
157 button_size = NWidgetScrollbar::GetVerticalDimension().height;
158 }
159 top += button_size; // top points to just below the up-button
160 bottom -= button_size; // bottom points to just before the down-button
161
162 int count = sb->GetCount();
163 int cap = sb->GetCapacity();
164
165 if (count > cap) {
166 int height = bottom + 1 - top;
167 int slider_height = std::max(button_size, cap * height / count);
168 height -= slider_height;
169
170 top += height * sb->GetPosition() / (count - cap);
171 bottom = top + slider_height - 1;
172 }
173
174 Point pt;
175 if (horizontal && _current_text_dir == TD_RTL) {
176 pt.x = rev_base - bottom;
177 pt.y = rev_base - top;
178 } else {
179 pt.x = top;
180 pt.y = bottom;
181 }
182 return pt;
183}
184
194static void ScrollbarClickPositioning(Window *w, NWidgetScrollbar *sb, int x, int y, int mi, int ma)
195{
196 int pos;
197 int button_size;
198 bool rtl = false;
199 bool changed = false;
200
201 if (sb->type == NWID_HSCROLLBAR) {
202 pos = x;
203 rtl = _current_text_dir == TD_RTL;
204 button_size = NWidgetScrollbar::GetHorizontalDimension().width;
205 } else {
206 pos = y;
207 button_size = NWidgetScrollbar::GetVerticalDimension().height;
208 }
209 if (pos < mi + button_size) {
210 /* Pressing the upper button? */
212 if (_scroller_click_timeout <= 1) {
213 _scroller_click_timeout = 3;
214 changed = sb->UpdatePosition(rtl ? 1 : -1);
215 }
216 w->mouse_capture_widget = sb->GetIndex();
217 } else if (pos >= ma - button_size) {
218 /* Pressing the lower button? */
220
221 if (_scroller_click_timeout <= 1) {
222 _scroller_click_timeout = 3;
223 changed = sb->UpdatePosition(rtl ? -1 : 1);
224 }
225 w->mouse_capture_widget = sb->GetIndex();
226 } else {
227 Point pt = HandleScrollbarHittest(sb, mi, ma, sb->type == NWID_HSCROLLBAR);
228
229 if (pos < pt.x) {
230 changed = sb->UpdatePosition(rtl ? 1 : -1, Scrollbar::SS_BIG);
231 } else if (pos > pt.y) {
232 changed = sb->UpdatePosition(rtl ? -1 : 1, Scrollbar::SS_BIG);
233 } else {
234 _scrollbar_start_pos = pt.x - mi - button_size;
235 _scrollbar_size = ma - mi - button_size * 2 - (pt.y - pt.x);
236 w->mouse_capture_widget = sb->GetIndex();
237 _cursorpos_drag_start = _cursor.pos;
238 }
239 }
240
241 if (changed) {
242 /* Position changed so refresh the window */
243 w->OnScrollbarScroll(sb->GetIndex());
244 w->SetDirty();
245 } else {
246 /* No change so only refresh this scrollbar */
247 sb->SetDirty(w);
248 }
249}
250
259void ScrollbarClickHandler(Window *w, NWidgetCore *nw, int x, int y)
260{
261 int mi, ma;
262
263 if (nw->type == NWID_HSCROLLBAR) {
264 mi = nw->pos_x;
265 ma = nw->pos_x + nw->current_x;
266 } else {
267 mi = nw->pos_y;
268 ma = nw->pos_y + nw->current_y;
269 }
270 NWidgetScrollbar *scrollbar = dynamic_cast<NWidgetScrollbar*>(nw);
271 assert(scrollbar != nullptr);
272 ScrollbarClickPositioning(w, scrollbar, x, y, mi, ma);
273}
274
283WidgetID GetWidgetFromPos(const Window *w, int x, int y)
284{
285 NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
286 return (nw != nullptr) ? nw->GetIndex() : -1;
287}
288
298void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
299{
300 if (flags.Test(FrameFlag::Transparent)) {
301 GfxFillRect(left, top, right, bottom, PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOUR);
302 } else {
303 assert(colour < COLOUR_END);
304
305 const uint dark = GetColourGradient(colour, SHADE_DARK);
306 const uint medium_dark = GetColourGradient(colour, SHADE_LIGHT);
307 const uint medium_light = GetColourGradient(colour, SHADE_LIGHTER);
308 const uint light = GetColourGradient(colour, SHADE_LIGHTEST);
309 uint interior;
310
311 Rect outer = {left, top, right, bottom}; // Outside rectangle
312 Rect inner = outer.Shrink(WidgetDimensions::scaled.bevel); // Inside rectangle
313
314 if (flags.Test(FrameFlag::Lowered)) {
315 GfxFillRect(outer.left, outer.top, inner.left - 1, outer.bottom, dark); // Left
316 GfxFillRect(inner.left, outer.top, outer.right, inner.top - 1, dark); // Top
317 GfxFillRect(inner.right + 1, inner.top, outer.right, inner.bottom, light); // Right
318 GfxFillRect(inner.left, inner.bottom + 1, outer.right, outer.bottom, light); // Bottom
319 interior = (flags.Test(FrameFlag::Darkened) ? medium_dark : medium_light);
320 } else {
321 GfxFillRect(outer.left, outer.top, inner.left - 1, inner.bottom, light); // Left
322 GfxFillRect(inner.left, outer.top, inner.right, inner.top - 1, light); // Top
323 GfxFillRect(inner.right + 1, outer.top, outer.right, inner.bottom, dark); // Right
324 GfxFillRect(outer.left, inner.bottom + 1, outer.right, outer.bottom, dark); // Bottom
325 interior = medium_dark;
326 }
327 if (!flags.Test(FrameFlag::BorderOnly)) {
328 GfxFillRect(inner.left, inner.top, inner.right, inner.bottom, interior); // Inner
329 }
330 }
331}
332
333void DrawSpriteIgnorePadding(SpriteID img, PaletteID pal, const Rect &r, StringAlignment align)
334{
335 Point offset;
336 Dimension d = GetSpriteSize(img, &offset);
337 d.width -= offset.x;
338 d.height -= offset.y;
339
340 Point p = GetAlignedPosition(r, d, align);
341 DrawSprite(img, pal, p.x - offset.x, p.y - offset.y);
342}
343
353static inline void DrawImageButtons(const Rect &r, WidgetType type, Colours colour, bool clicked, SpriteID img, StringAlignment align)
354{
355 assert(img != 0);
356 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
357
358 if ((type & WWT_MASK) == WWT_IMGBTN_2 && clicked) img++; // Show different image when clicked for #WWT_IMGBTN_2.
359 DrawSpriteIgnorePadding(img, PAL_NONE, r, align);
360}
361
373static inline void DrawImageTextButtons(const Rect &r, Colours colour, bool clicked, SpriteID img, TextColour text_colour, const std::string &text, StringAlignment align, FontSize fs)
374{
375 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
376
377 bool rtl = _current_text_dir == TD_RTL;
378 int image_width = img != 0 ? GetScaledSpriteSize(img).width : 0;
379 Rect r_img = r.Shrink(WidgetDimensions::scaled.framerect).WithWidth(image_width, rtl);
380 Rect r_text = r.Shrink(WidgetDimensions::scaled.framerect).Indent(image_width + WidgetDimensions::scaled.hsep_wide, rtl);
381
382 if (img != 0) {
383 DrawSpriteIgnorePadding(img, PAL_NONE, r_img, SA_HOR_CENTER | (align & SA_VERT_MASK));
384 }
385
386 if (!text.empty()) {
387 Dimension d = GetStringBoundingBox(text, fs);
388 Point p = GetAlignedPosition(r_text, d, align);
389 DrawString(r_text.left, r_text.right, p.y, text, text_colour, align, false, fs);
390 }
391}
392
401static inline void DrawLabel(const Rect &r, TextColour colour, std::string_view str, StringAlignment align, FontSize fs)
402{
403 if (str.empty()) return;
404
405 Dimension d = GetStringBoundingBox(str, fs);
406 Point p = GetAlignedPosition(r, d, align);
407 DrawString(r.left, r.right, p.y, str, colour, align, false, fs);
408}
409
418static inline void DrawText(const Rect &r, TextColour colour, std::string_view str, StringAlignment align, FontSize fs)
419{
420 if (str.empty()) return;
421
422 Dimension d = GetStringBoundingBox(str, fs);
423 Point p = GetAlignedPosition(r, d, align);
424 DrawString(r.left, r.right, p.y, str, colour, align, false, fs);
425}
426
436static inline void DrawInset(const Rect &r, Colours colour, TextColour text_colour, std::string_view str, StringAlignment align, FontSize fs)
437{
438 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, {FrameFlag::Lowered, FrameFlag::Darkened});
439 if (!str.empty()) DrawString(r.Shrink(WidgetDimensions::scaled.inset), str, text_colour, align, false, fs);
440}
441
452static inline void DrawMatrix(const Rect &r, Colours colour, bool clicked, uint32_t num_columns, uint32_t num_rows, uint resize_x, uint resize_y)
453{
454 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
455
456 int column_width; // Width of a single column in the matrix.
457 if (num_columns == 0) {
458 column_width = resize_x;
459 num_columns = r.Width() / column_width;
460 } else {
461 column_width = r.Width() / num_columns;
462 }
463
464 int row_height; // Height of a single row in the matrix.
465 if (num_rows == 0) {
466 row_height = resize_y;
467 num_rows = r.Height() / row_height;
468 } else {
469 row_height = r.Height() / num_rows;
470 }
471
472 int col = GetColourGradient(colour, SHADE_LIGHTER);
473
474 int x = r.left;
475 for (int ctr = num_columns; ctr > 1; ctr--) {
476 x += column_width;
477 GfxFillRect(x, r.top + WidgetDimensions::scaled.bevel.top, x + WidgetDimensions::scaled.bevel.left - 1, r.bottom - WidgetDimensions::scaled.bevel.bottom, col);
478 }
479
480 x = r.top;
481 for (int ctr = num_rows; ctr > 1; ctr--) {
482 x += row_height;
484 }
485
486 col = GetColourGradient(colour, SHADE_NORMAL);
487
488 x = r.left - 1;
489 for (int ctr = num_columns; ctr > 1; ctr--) {
490 x += column_width;
491 GfxFillRect(x - WidgetDimensions::scaled.bevel.right + 1, r.top + WidgetDimensions::scaled.bevel.top, x, r.bottom - WidgetDimensions::scaled.bevel.bottom, col);
492 }
493
494 x = r.top - 1;
495 for (int ctr = num_rows; ctr > 1; ctr--) {
496 x += row_height;
497 GfxFillRect(r.left + WidgetDimensions::scaled.bevel.left, x - WidgetDimensions::scaled.bevel.bottom + 1, r.right - WidgetDimensions::scaled.bevel.right, x, col);
498 }
499}
500
510static inline void DrawVerticalScrollbar(const Rect &r, Colours colour, bool up_clicked, bool bar_dragged, bool down_clicked, const Scrollbar *scrollbar)
511{
512 int height = NWidgetScrollbar::GetVerticalDimension().height;
513
514 /* draw up/down buttons */
515 DrawImageButtons(r.WithHeight(height, false), NWID_VSCROLLBAR, colour, up_clicked, SPR_ARROW_UP, SA_CENTER);
516 DrawImageButtons(r.WithHeight(height, true), NWID_VSCROLLBAR, colour, down_clicked, SPR_ARROW_DOWN, SA_CENTER);
517
518 int c1 = GetColourGradient(colour, SHADE_DARK);
519 int c2 = GetColourGradient(colour, SHADE_LIGHTEST);
520
521 /* draw "shaded" background */
522 GfxFillRect(r.left, r.top + height, r.right, r.bottom - height, c2);
523 GfxFillRect(r.left, r.top + height, r.right, r.bottom - height, c1, FILLRECT_CHECKER);
524
525 /* track positions. These fractions are based on original 1x dimensions, but scale better. */
526 int left = r.left + r.Width() * 3 / 11; /* left track is positioned 3/11ths from the left */
527 int right = r.left + r.Width() * 8 / 11; /* right track is positioned 8/11ths from the left */
528 const uint8_t bl = WidgetDimensions::scaled.bevel.left;
529 const uint8_t br = WidgetDimensions::scaled.bevel.right;
530
531 /* draw shaded lines */
532 GfxFillRect(left - bl, r.top + height, left - 1, r.bottom - height, c1);
533 GfxFillRect(left, r.top + height, left + br - 1, r.bottom - height, c2);
534 GfxFillRect(right - bl, r.top + height, right - 1, r.bottom - height, c1);
535 GfxFillRect(right, r.top + height, right + br - 1, r.bottom - height, c2);
536
537 Point pt = HandleScrollbarHittest(scrollbar, r.top, r.bottom, false);
538 DrawFrameRect(r.left, pt.x, r.right, pt.y, colour, bar_dragged ? FrameFlag::Lowered : FrameFlags{});
539}
540
550static inline void DrawHorizontalScrollbar(const Rect &r, Colours colour, bool left_clicked, bool bar_dragged, bool right_clicked, const Scrollbar *scrollbar)
551{
552 int width = NWidgetScrollbar::GetHorizontalDimension().width;
553
554 DrawImageButtons(r.WithWidth(width, false), NWID_HSCROLLBAR, colour, left_clicked, SPR_ARROW_LEFT, SA_CENTER);
555 DrawImageButtons(r.WithWidth(width, true), NWID_HSCROLLBAR, colour, right_clicked, SPR_ARROW_RIGHT, SA_CENTER);
556
557 int c1 = GetColourGradient(colour, SHADE_DARK);
558 int c2 = GetColourGradient(colour, SHADE_LIGHTEST);
559
560 /* draw "shaded" background */
561 GfxFillRect(r.left + width, r.top, r.right - width, r.bottom, c2);
562 GfxFillRect(r.left + width, r.top, r.right - width, r.bottom, c1, FILLRECT_CHECKER);
563
564 /* track positions. These fractions are based on original 1x dimensions, but scale better. */
565 int top = r.top + r.Height() * 3 / 11; /* top track is positioned 3/11ths from the top */
566 int bottom = r.top + r.Height() * 8 / 11; /* bottom track is positioned 8/11ths from the top */
567 const uint8_t bt = WidgetDimensions::scaled.bevel.top;
568 const uint8_t bb = WidgetDimensions::scaled.bevel.bottom;
569
570 /* draw shaded lines */
571 GfxFillRect(r.left + width, top - bt, r.right - width, top - 1, c1);
572 GfxFillRect(r.left + width, top, r.right - width, top + bb - 1, c2);
573 GfxFillRect(r.left + width, bottom - bt, r.right - width, bottom - 1, c1);
574 GfxFillRect(r.left + width, bottom, r.right - width, bottom + bb - 1, c2);
575
576 /* draw actual scrollbar */
577 Point pt = HandleScrollbarHittest(scrollbar, r.left, r.right, true);
578 DrawFrameRect(pt.x, r.top, pt.y, r.bottom, colour, bar_dragged ? FrameFlag::Lowered : FrameFlags{});
579}
580
590static inline void DrawFrame(const Rect &r, Colours colour, TextColour text_colour, std::string_view str, StringAlignment align, FontSize fs)
591{
592 int x2 = r.left; // by default the left side is the left side of the widget
593
594 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);
595
596 int c1 = GetColourGradient(colour, SHADE_DARK);
597 int c2 = GetColourGradient(colour, SHADE_LIGHTEST);
598
599 /* If the frame has text, adjust the top bar to fit half-way through */
600 Rect inner = r.Shrink(ScaleGUITrad(1));
601 if (!str.empty()) inner.top = r.top + GetCharacterHeight(FS_NORMAL) / 2;
602
603 Rect outer = inner.Expand(WidgetDimensions::scaled.bevel);
604 Rect inside = inner.Shrink(WidgetDimensions::scaled.bevel);
605
606 if (_current_text_dir == TD_LTR) {
607 /* Line from upper left corner to start of text */
608 GfxFillRect(outer.left, outer.top, r.left + WidgetDimensions::scaled.frametext.left - WidgetDimensions::scaled.bevel.left - 1, inner.top - 1, c1);
609 GfxFillRect(inner.left, inner.top, r.left + WidgetDimensions::scaled.frametext.left - WidgetDimensions::scaled.bevel.left - 1, inside.top - 1, c2);
610
611 /* Line from end of text to upper right corner */
612 GfxFillRect(x2 + WidgetDimensions::scaled.bevel.right, outer.top, inner.right, inner.top - 1, c1);
613 GfxFillRect(x2 + WidgetDimensions::scaled.bevel.right, inner.top, inside.right, inside.top - 1, c2);
614 } else {
615 /* Line from upper left corner to start of text */
616 GfxFillRect(outer.left, outer.top, x2 - WidgetDimensions::scaled.bevel.left - 1, inner.top - 1, c1);
617 GfxFillRect(inner.left, inner.top, x2 - WidgetDimensions::scaled.bevel.left - 1, inside.top - 1, c2);
618
619 /* Line from end of text to upper right corner */
620 GfxFillRect(r.right - WidgetDimensions::scaled.frametext.right + WidgetDimensions::scaled.bevel.right, outer.top, inner.right, inner.top - 1, c1);
621 GfxFillRect(r.right - WidgetDimensions::scaled.frametext.right + WidgetDimensions::scaled.bevel.right, inner.top, inside.right, inside.top - 1, c2);
622 }
623
624 /* Line from upper left corner to bottom left corner */
625 GfxFillRect(outer.left, inner.top, inner.left - 1, inner.bottom, c1);
626 GfxFillRect(inner.left, inside.top, inside.left - 1, inside.bottom, c2);
627
628 /* Line from upper right corner to bottom right corner */
629 GfxFillRect(inside.right + 1, inner.top, inner.right, inside.bottom, c1);
630 GfxFillRect(inner.right + 1, outer.top, outer.right, inner.bottom, c2);
631
632 /* Line from bottom left corner to bottom right corner */
633 GfxFillRect(inner.left, inside.bottom + 1, inner.right, inner.bottom, c1);
634 GfxFillRect(outer.left, inner.bottom + 1, outer.right, outer.bottom, c2);
635}
636
643static inline void DrawShadeBox(const Rect &r, Colours colour, bool clicked)
644{
645 DrawImageButtons(r, WWT_SHADEBOX, colour, clicked, clicked ? SPR_WINDOW_SHADE: SPR_WINDOW_UNSHADE, SA_CENTER);
646}
647
654static inline void DrawStickyBox(const Rect &r, Colours colour, bool clicked)
655{
656 DrawImageButtons(r, WWT_STICKYBOX, colour, clicked, clicked ? SPR_PIN_UP : SPR_PIN_DOWN, SA_CENTER);
657}
658
665static inline void DrawDefSizeBox(const Rect &r, Colours colour, bool clicked)
666{
667 DrawImageButtons(r, WWT_DEFSIZEBOX, colour, clicked, SPR_WINDOW_DEFSIZE, SA_CENTER);
668}
669
676static inline void DrawDebugBox(const Rect &r, Colours colour, bool clicked)
677{
678 DrawImageButtons(r, WWT_DEBUGBOX, colour, clicked, SPR_WINDOW_DEBUG, SA_CENTER);
679}
680
689static inline void DrawResizeBox(const Rect &r, Colours colour, bool at_left, bool clicked, bool bevel)
690{
691 if (bevel) {
692 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
693 } else if (clicked) {
695 }
696 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));
697}
698
704static inline void DrawCloseBox(const Rect &r, Colours colour)
705{
706 if (colour != COLOUR_WHITE) DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, {});
707 Point offset;
708 Dimension d = GetSpriteSize(SPR_CLOSEBOX, &offset);
709 d.width -= offset.x;
710 d.height -= offset.y;
711 int s = ScaleSpriteTrad(1); /* Offset to account for shadow of SPR_CLOSEBOX */
712 DrawSprite(SPR_CLOSEBOX, (colour != COLOUR_WHITE ? TC_BLACK : TC_SILVER) | (1U << PALETTE_TEXT_RECOLOUR), CentreBounds(r.left, r.right, d.width - s) - offset.x, CentreBounds(r.top, r.bottom, d.height - s) - offset.y);
713}
714
725void DrawCaption(const Rect &r, Colours colour, Owner owner, TextColour text_colour, std::string_view str, StringAlignment align, FontSize fs)
726{
727 bool company_owned = owner < MAX_COMPANIES;
728
732
733 if (company_owned) {
735 }
736
737 if (str.empty()) return;
738
740 Point p = GetAlignedPosition(r, d, align);
741 DrawString(r.left + WidgetDimensions::scaled.captiontext.left, r.right - WidgetDimensions::scaled.captiontext.left, p.y, str, text_colour, align, false, fs);
742}
743
755static inline void DrawButtonDropdown(const Rect &r, Colours colour, bool clicked_button, bool clicked_dropdown, std::string_view str, StringAlignment align)
756{
757 int dd_width = NWidgetLeaf::dropdown_dimension.width;
758
759 if (_current_text_dir == TD_LTR) {
760 DrawFrameRect(r.left, r.top, r.right - dd_width, r.bottom, colour, clicked_button ? FrameFlag::Lowered : FrameFlags{});
761 DrawImageButtons(r.WithWidth(dd_width, true), WWT_DROPDOWN, colour, clicked_dropdown, SPR_ARROW_DOWN, SA_CENTER);
762 if (!str.empty()) {
763 DrawString(r.left + WidgetDimensions::scaled.dropdowntext.left, r.right - dd_width - WidgetDimensions::scaled.dropdowntext.right, CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), str, TC_BLACK, align);
764 }
765 } else {
766 DrawFrameRect(r.left + dd_width, r.top, r.right, r.bottom, colour, clicked_button ? FrameFlag::Lowered : FrameFlags{});
767 DrawImageButtons(r.WithWidth(dd_width, false), WWT_DROPDOWN, colour, clicked_dropdown, SPR_ARROW_DOWN, SA_CENTER);
768 if (!str.empty()) {
769 DrawString(r.left + dd_width + WidgetDimensions::scaled.dropdowntext.left, r.right - WidgetDimensions::scaled.dropdowntext.right, CentreBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), str, TC_BLACK, align);
770 }
771 }
772}
773
778{
779 this->nested_root->Draw(this);
780
782 DrawFrameRect(0, 0, this->width - 1, this->height - 1, COLOUR_WHITE, FrameFlag::BorderOnly);
783 }
784
786 extern bool _window_highlight_colour;
787 for (const auto &pair : this->widget_lookup) {
788 const NWidgetBase *widget = pair.second;
789 if (!widget->IsHighlighted()) continue;
790
791 Rect outer = widget->GetCurrentRect();
792 Rect inner = outer.Shrink(WidgetDimensions::scaled.bevel).Expand(1);
793
794 int colour = _string_colourmap[_window_highlight_colour ? widget->GetHighlightColour() : TC_WHITE];
795
796 GfxFillRect(outer.left, outer.top, inner.left, inner.bottom, colour);
797 GfxFillRect(inner.left + 1, outer.top, inner.right - 1, inner.top, colour);
798 GfxFillRect(inner.right, outer.top, outer.right, inner.bottom, colour);
799 GfxFillRect(outer.left + 1, inner.bottom, outer.right - 1, outer.bottom, colour);
800 }
801 }
802}
803
810{
811 if (state == SBS_OFF) return;
812
813 assert(!this->widget_lookup.empty());
814 Rect r = this->GetWidget<NWidgetBase>(widget)->GetCurrentRect();
815
816 /* Sort button uses the same sprites as vertical scrollbar */
817 Dimension dim = NWidgetScrollbar::GetVerticalDimension();
818
819 DrawSpriteIgnorePadding(state == SBS_DOWN ? SPR_ARROW_DOWN : SPR_ARROW_UP, PAL_NONE, r.WithWidth(dim.width, _current_text_dir == TD_LTR), SA_CENTER);
820}
821
827{
828 return NWidgetScrollbar::GetVerticalDimension().width + 1;
829}
830
831bool _draw_widget_outlines;
832
833static void DrawOutline(const Window *, const NWidgetBase *wid)
834{
835 if (!_draw_widget_outlines || wid->current_x == 0 || wid->current_y == 0) return;
836
837 DrawRectOutline(wid->GetCurrentRect(), PC_WHITE, 1, 4);
838}
839
927{
928 if (this->index >= 0) widget_lookup[this->index] = this;
929}
930
942void NWidgetBase::SetDirty(const Window *w) const
943{
944 int abs_left = w->left + this->pos_x;
945 int abs_top = w->top + this->pos_y;
946 AddDirtyBlock(abs_left, abs_top, abs_left + this->current_x, abs_top + this->current_y);
947}
948
963{
964 return (this->type == tp) ? this : nullptr;
965}
966
967void NWidgetBase::ApplyAspectRatio()
968{
969 if (this->aspect_ratio == 0) return;
970 if (this->smallest_x == 0 || this->smallest_y == 0) return;
971
972 uint x = this->smallest_x;
973 uint y = this->smallest_y;
974 if (this->aspect_flags.Test(AspectFlag::ResizeX)) x = std::max(this->smallest_x, static_cast<uint>(this->smallest_y * std::abs(this->aspect_ratio)));
975 if (this->aspect_flags.Test(AspectFlag::ResizeY)) y = std::max(this->smallest_y, static_cast<uint>(this->smallest_x / std::abs(this->aspect_ratio)));
976
977 this->smallest_x = x;
978 this->smallest_y = y;
979}
980
981void NWidgetBase::AdjustPaddingForZoom()
982{
983 this->padding = ScaleGUITrad(this->uz_padding);
984}
985
992NWidgetResizeBase::NWidgetResizeBase(WidgetType tp, WidgetID index, uint fill_x, uint fill_y) : NWidgetBase(tp, index)
993{
994 this->fill_x = fill_x;
995 this->fill_y = fill_y;
996}
997
1004{
1005 this->aspect_ratio = ratio;
1006 this->aspect_flags = flags;
1007}
1008
1015void NWidgetResizeBase::SetAspect(int x_ratio, int y_ratio, AspectFlags flags)
1016{
1017 this->SetAspect(static_cast<float>(x_ratio) / static_cast<float>(y_ratio), flags);
1018}
1019
1020void NWidgetResizeBase::AdjustPaddingForZoom()
1021{
1022 if (!this->absolute) {
1023 this->min_x = ScaleGUITrad(this->uz_min_x);
1024 this->min_y = std::max(ScaleGUITrad(this->uz_min_y), this->uz_text_lines * GetCharacterHeight(this->uz_text_size) + ScaleGUITrad(this->uz_text_spacing));
1025 }
1026 NWidgetBase::AdjustPaddingForZoom();
1027}
1028
1034void NWidgetResizeBase::SetMinimalSize(uint min_x, uint min_y)
1035{
1036 this->uz_min_x = std::max(this->uz_min_x, min_x);
1037 this->uz_min_y = std::max(this->uz_min_y, min_y);
1038 this->min_x = ScaleGUITrad(this->uz_min_x);
1039 this->min_y = std::max(ScaleGUITrad(this->uz_min_y), this->uz_text_lines * GetCharacterHeight(this->uz_text_size) + ScaleGUITrad(this->uz_text_spacing));
1040}
1041
1048{
1049 this->absolute = true;
1050 this->min_x = std::max(this->min_x, min_x);
1051 this->min_y = std::max(this->min_y, min_y);
1052}
1053
1060void NWidgetResizeBase::SetMinimalTextLines(uint8_t min_lines, uint8_t spacing, FontSize size)
1061{
1062 this->uz_text_lines = min_lines;
1063 this->uz_text_spacing = spacing;
1064 this->uz_text_size = size;
1065 this->min_y = std::max(ScaleGUITrad(this->uz_min_y), this->uz_text_lines * GetCharacterHeight(this->uz_text_size) + ScaleGUITrad(this->uz_text_spacing));
1066}
1067
1073void NWidgetResizeBase::SetFill(uint fill_x, uint fill_y)
1074{
1075 this->fill_x = fill_x;
1076 this->fill_y = fill_y;
1077}
1078
1084void NWidgetResizeBase::SetResize(uint resize_x, uint resize_y)
1085{
1086 this->resize_x = resize_x;
1087 this->resize_y = resize_y;
1088}
1089
1097bool NWidgetResizeBase::UpdateMultilineWidgetSize(const std::string &str, int max_lines)
1098{
1099 int y = GetStringHeight(str, this->current_x);
1100 if (y > max_lines * GetCharacterHeight(FS_NORMAL)) {
1101 /* Text at the current width is too tall, so try to guess a better width. */
1103 d.height *= max_lines;
1104 d.width /= 2;
1105 return this->UpdateSize(d.width, d.height);
1106 }
1107 return this->UpdateVerticalSize(y);
1108}
1109
1117bool NWidgetResizeBase::UpdateSize(uint min_x, uint min_y)
1118{
1119 if (min_x == this->min_x && min_y == this->min_y) return false;
1120 this->min_x = min_x;
1121 this->min_y = min_y;
1122 return true;
1123}
1124
1132{
1133 if (min_y == this->min_y) return false;
1134 this->min_y = min_y;
1135 return true;
1136}
1137
1138void NWidgetResizeBase::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool)
1139{
1140 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1141}
1142
1153NWidgetCore::NWidgetCore(WidgetType tp, Colours colour, WidgetID index, uint fill_x, uint fill_y, const WidgetData &widget_data, StringID tool_tip) : NWidgetResizeBase(tp, index, fill_x, fill_y)
1154{
1155 this->colour = colour;
1156 this->widget_data = widget_data;
1157 this->SetToolTip(tool_tip);
1158 this->text_colour = tp == WWT_CAPTION ? TC_WHITE : TC_BLACK;
1159}
1160
1166{
1167 this->widget_data.string = string;
1168}
1169
1176{
1177 this->SetString(string);
1178 this->SetToolTip(tool_tip);
1179}
1180
1186{
1187 this->widget_data.sprite = sprite;
1188}
1189
1196{
1197 this->SetSprite(sprite);
1198 this->SetToolTip(tool_tip);
1199}
1200
1206void NWidgetCore::SetMatrixDimension(uint32_t columns, uint32_t rows)
1207{
1208 this->widget_data.matrix = { columns, rows };
1209}
1210
1216{
1217 this->widget_data.resize_widget_type = type;
1218}
1219
1226{
1227 this->text_colour = colour;
1228 this->text_size = size;
1229}
1230
1236{
1237 this->tool_tip = tool_tip;
1238}
1239
1245{
1246 return this->tool_tip;
1247}
1248
1254{
1255 this->align = align;
1256}
1257
1263{
1264 return this->widget_data.string;
1265}
1266
1272{
1273 return this->scrollbar_index;
1274}
1275
1277{
1278 return (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) ? this : nullptr;
1279}
1280
1282{
1283 if (this->type == tp) return this;
1284 for (const auto &child_wid : this->children) {
1285 NWidgetBase *nwid = child_wid->GetWidgetOfType(tp);
1286 if (nwid != nullptr) return nwid;
1287 }
1288 return nullptr;
1289}
1290
1291void NWidgetContainer::AdjustPaddingForZoom()
1292{
1293 for (const auto &child_wid : this->children) {
1294 child_wid->AdjustPaddingForZoom();
1295 }
1296 NWidgetBase::AdjustPaddingForZoom();
1297}
1298
1303void NWidgetContainer::Add(std::unique_ptr<NWidgetBase> &&wid)
1304{
1305 assert(wid != nullptr);
1306 wid->parent = this;
1307 this->children.push_back(std::move(wid));
1308}
1309
1311{
1312 this->NWidgetBase::FillWidgetLookup(widget_lookup);
1313 for (const auto &child_wid : this->children) {
1314 child_wid->FillWidgetLookup(widget_lookup);
1315 }
1316}
1317
1319{
1320 for (const auto &child_wid : this->children) {
1321 child_wid->Draw(w);
1322 }
1323
1324 DrawOutline(w, this);
1325}
1326
1328{
1329 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
1330
1331 for (const auto &child_wid : this->children) {
1332 NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
1333 if (nwid != nullptr) return nwid;
1334 }
1335 return nullptr;
1336}
1337
1339{
1340 /* Zero size plane selected */
1341 if (this->shown_plane >= SZSP_BEGIN) {
1342 Dimension size = {0, 0};
1343 Dimension padding = {0, 0};
1344 Dimension fill = {(this->shown_plane == SZSP_HORIZONTAL), (this->shown_plane == SZSP_VERTICAL)};
1345 Dimension resize = {(this->shown_plane == SZSP_HORIZONTAL), (this->shown_plane == SZSP_VERTICAL)};
1346 /* Here we're primarily interested in the value of resize */
1347 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
1348
1349 this->smallest_x = size.width;
1350 this->smallest_y = size.height;
1351 this->fill_x = fill.width;
1352 this->fill_y = fill.height;
1353 this->resize_x = resize.width;
1354 this->resize_y = resize.height;
1355 this->ApplyAspectRatio();
1356 return;
1357 }
1358
1359 /* First sweep, recurse down and compute minimal size and filling. */
1360 this->smallest_x = 0;
1361 this->smallest_y = 0;
1362 this->fill_x = this->IsEmpty() ? 0 : 1;
1363 this->fill_y = this->IsEmpty() ? 0 : 1;
1364 this->resize_x = this->IsEmpty() ? 0 : 1;
1365 this->resize_y = this->IsEmpty() ? 0 : 1;
1366 for (const auto &child_wid : this->children) {
1367 child_wid->SetupSmallestSize(w);
1368
1369 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1370 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1371 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1372 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1373 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1374 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1375 this->ApplyAspectRatio();
1376 }
1377}
1378
1379void NWidgetStacked::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1380{
1381 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1382 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1383
1384 if (this->shown_plane >= SZSP_BEGIN) return;
1385
1386 for (const auto &child_wid : this->children) {
1387 uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1388 uint child_width = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1389 uint child_pos_x = (rtl ? child_wid->padding.right : child_wid->padding.left);
1390
1391 uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1392 uint child_height = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1393 uint child_pos_y = child_wid->padding.top;
1394
1395 child_wid->AssignSizePosition(sizing, x + child_pos_x, y + child_pos_y, child_width, child_height, rtl);
1396 }
1397}
1398
1400{
1401 /* We need to update widget_lookup later. */
1402 this->widget_lookup = &widget_lookup;
1403
1404 this->NWidgetContainer::FillWidgetLookup(widget_lookup);
1405 /* In case widget IDs are repeated, make sure Window::GetWidget works on displayed widgets. */
1406 if (static_cast<size_t>(this->shown_plane) < this->children.size()) this->children[shown_plane]->FillWidgetLookup(widget_lookup);
1407}
1408
1410{
1411 if (this->shown_plane >= SZSP_BEGIN) return;
1412
1413 assert(static_cast<size_t>(this->shown_plane) < this->children.size());
1414 this->children[shown_plane]->Draw(w);
1415 DrawOutline(w, this);
1416}
1417
1419{
1420 if (this->shown_plane >= SZSP_BEGIN) return nullptr;
1421
1422 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
1423
1424 if (static_cast<size_t>(this->shown_plane) >= this->children.size()) return nullptr;
1425 return this->children[shown_plane]->GetWidgetFromPos(x, y);
1426}
1427
1434{
1435 if (this->shown_plane == plane) return false;
1436 this->shown_plane = plane;
1437 /* In case widget IDs are repeated, make sure Window::GetWidget works on displayed widgets. */
1438 if (static_cast<size_t>(this->shown_plane) < this->children.size()) this->children[shown_plane]->FillWidgetLookup(*this->widget_lookup);
1439 return true;
1440}
1441
1443public:
1445
1446 void SetupSmallestSize(Window *w) override;
1447 void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override;
1448
1449 void Draw(const Window *w) override;
1450};
1451
1453{
1454 /* First sweep, recurse down and compute minimal size and filling. */
1455 this->smallest_x = 0;
1456 this->smallest_y = 0;
1457 this->fill_x = this->IsEmpty() ? 0 : 1;
1458 this->fill_y = this->IsEmpty() ? 0 : 1;
1459 this->resize_x = this->IsEmpty() ? 0 : 1;
1460 this->resize_y = this->IsEmpty() ? 0 : 1;
1461 for (const auto &child_wid : this->children) {
1462 child_wid->SetupSmallestSize(w);
1463
1464 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1465 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1466 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1467 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1468 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1469 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1470 this->ApplyAspectRatio();
1471 }
1472}
1473
1474void NWidgetLayer::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1475{
1476 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1477 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1478
1479 for (const auto &child_wid : this->children) {
1480 uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1481 uint child_width = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1482 uint child_pos_x = (rtl ? child_wid->padding.right : child_wid->padding.left);
1483
1484 uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1485 uint child_height = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1486 uint child_pos_y = child_wid->padding.top;
1487
1488 child_wid->AssignSizePosition(sizing, x + child_pos_x, y + child_pos_y, child_width, child_height, rtl);
1489 }
1490}
1491
1493{
1494 /* Draw in reverse order, as layers are arranged top-down. */
1495 for (auto it = std::rbegin(this->children); it != std::rend(this->children); ++it) {
1496 (*it)->Draw(w);
1497 }
1498
1499 DrawOutline(w, this);
1500}
1501
1502void NWidgetPIPContainer::AdjustPaddingForZoom()
1503{
1504 this->pip_pre = ScaleGUITrad(this->uz_pip_pre);
1505 this->pip_inter = ScaleGUITrad(this->uz_pip_inter);
1506 this->pip_post = ScaleGUITrad(this->uz_pip_post);
1507 NWidgetContainer::AdjustPaddingForZoom();
1508}
1509
1519void NWidgetPIPContainer::SetPIP(uint8_t pip_pre, uint8_t pip_inter, uint8_t pip_post)
1520{
1521 this->uz_pip_pre = pip_pre;
1522 this->uz_pip_inter = pip_inter;
1523 this->uz_pip_post = pip_post;
1524
1525 this->pip_pre = ScaleGUITrad(this->uz_pip_pre);
1526 this->pip_inter = ScaleGUITrad(this->uz_pip_inter);
1527 this->pip_post = ScaleGUITrad(this->uz_pip_post);
1528}
1529
1539void NWidgetPIPContainer::SetPIPRatio(uint8_t pip_ratio_pre, uint8_t pip_ratio_inter, uint8_t pip_ratio_post)
1540{
1541 this->pip_ratio_pre = pip_ratio_pre;
1542 this->pip_ratio_inter = pip_ratio_inter;
1543 this->pip_ratio_post = pip_ratio_post;
1544}
1545
1547{
1548 this->smallest_x = 0; // Sum of minimal size of all children.
1549 this->smallest_y = 0; // Biggest child.
1550 this->fill_x = 0; // smallest non-zero child widget fill step.
1551 this->fill_y = 1; // smallest common child fill step.
1552 this->resize_x = 0; // smallest non-zero child widget resize step.
1553 this->resize_y = 1; // smallest common child resize step.
1554 this->gaps = 0;
1555
1556 /* 1a. Forward call, collect longest/widest child length. */
1557 uint longest = 0; // Longest child found.
1558 uint max_vert_fill = 0; // Biggest vertical fill step.
1559 for (const auto &child_wid : this->children) {
1560 child_wid->SetupSmallestSize(w);
1561 longest = std::max(longest, child_wid->smallest_x);
1562 max_vert_fill = std::max(max_vert_fill, child_wid->GetVerticalStepSize(ST_SMALLEST));
1563 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1564 if (child_wid->smallest_x != 0 || child_wid->fill_x != 0) this->gaps++;
1565 }
1566 if (this->gaps > 0) this->gaps--; // Number of gaps is number of widgets less one.
1567 /* 1b. Make the container higher if needed to accommodate all children nicely. */
1568 [[maybe_unused]] uint max_smallest = this->smallest_y + 3 * max_vert_fill; // Upper limit to computing smallest height.
1569 uint cur_height = this->smallest_y;
1570 for (;;) {
1571 for (const auto &child_wid : this->children) {
1572 uint step_size = child_wid->GetVerticalStepSize(ST_SMALLEST);
1573 uint child_height = child_wid->smallest_y + child_wid->padding.Vertical();
1574 if (step_size > 1 && child_height < cur_height) { // Small step sizes or already fitting children are not interesting.
1575 uint remainder = (cur_height - child_height) % step_size;
1576 if (remainder > 0) { // Child did not fit entirely, widen the container.
1577 cur_height += step_size - remainder;
1578 assert(cur_height < max_smallest); // Safeguard against infinite height expansion.
1579 /* Remaining children will adapt to the new cur_height, thus speeding up the computation. */
1580 }
1581 }
1582 }
1583 if (this->smallest_y == cur_height) break;
1584 this->smallest_y = cur_height; // Smallest height got changed, try again.
1585 }
1586 /* 2. For containers that must maintain equal width, extend child minimal size. */
1587 for (const auto &child_wid : this->children) {
1588 child_wid->smallest_y = this->smallest_y - child_wid->padding.Vertical();
1589 child_wid->ApplyAspectRatio();
1590 longest = std::max(longest, child_wid->smallest_x);
1591 }
1593 for (const auto &child_wid : this->children) {
1594 if (child_wid->fill_x == 1) child_wid->smallest_x = longest;
1595 }
1596 }
1597 /* 3. Compute smallest, fill, and resize values of the container. */
1598 for (const auto &child_wid : this->children) {
1599 this->smallest_x += child_wid->smallest_x + child_wid->padding.Horizontal();
1600 if (child_wid->fill_x > 0) {
1601 if (this->fill_x == 0 || this->fill_x > child_wid->fill_x) this->fill_x = child_wid->fill_x;
1602 }
1603 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1604
1605 if (child_wid->resize_x > 0) {
1606 if (this->resize_x == 0 || this->resize_x > child_wid->resize_x) this->resize_x = child_wid->resize_x;
1607 }
1608 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1609 }
1610 if (this->fill_x == 0 && this->pip_ratio_pre + this->pip_ratio_inter + this->pip_ratio_post > 0) this->fill_x = 1;
1611 /* 4. Increase by required PIP space. */
1612 this->smallest_x += this->pip_pre + this->gaps * this->pip_inter + this->pip_post;
1613}
1614
1615void NWidgetHorizontal::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1616{
1617 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1618
1619 /* Compute additional width given to us. */
1620 uint additional_length = given_width - (this->pip_pre + this->gaps * this->pip_inter + this->pip_post);
1621 for (const auto &child_wid : this->children) {
1622 if (child_wid->smallest_x != 0 || child_wid->fill_x != 0) additional_length -= child_wid->smallest_x + child_wid->padding.Horizontal();
1623 }
1624
1625 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1626
1627 /* In principle, the additional horizontal space is distributed evenly over the available resizable children. Due to step sizes, this may not always be feasible.
1628 * To make resizing work as good as possible, first children with biggest step sizes are done. These may get less due to rounding down.
1629 * 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
1630 * of the child with the smallest non-zero stepsize.
1631 *
1632 * 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
1633 * size and position, and directly call child->AssignSizePosition() with the computed values.
1634 * 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
1635 * then we call the child.
1636 */
1637
1638 /* First loop: Find biggest stepsize, find number of children that want a piece of the pie, handle vertical size for all children,
1639 * handle horizontal size for non-resizing children.
1640 */
1641 int num_changing_childs = 0; // Number of children that can change size.
1642 uint biggest_stepsize = 0;
1643 for (const auto &child_wid : this->children) {
1644 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1645 if (hor_step > 0) {
1646 if (!flags.Test(NWidContainerFlag::BigFirst)) num_changing_childs++;
1647 biggest_stepsize = std::max(biggest_stepsize, hor_step);
1648 } else {
1649 child_wid->current_x = child_wid->smallest_x;
1650 }
1651
1652 uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1653 child_wid->current_y = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1654 }
1655
1656 /* First.5 loop: count how many children are of the biggest step size. */
1657 if (flags.Test(NWidContainerFlag::BigFirst) && 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) {
1661 num_changing_childs++;
1662 }
1663 }
1664 }
1665
1666 /* Second loop: Allocate the additional horizontal space over the resizing children, starting with the biggest resize steps. */
1667 while (biggest_stepsize > 0) {
1668 uint next_biggest_stepsize = 0;
1669 for (const auto &child_wid : this->children) {
1670 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1671 if (hor_step > biggest_stepsize) continue; // Already done
1672 if (hor_step == biggest_stepsize) {
1673 uint increment = additional_length / num_changing_childs;
1674 num_changing_childs--;
1675 if (hor_step > 1) increment -= increment % hor_step;
1676 child_wid->current_x = child_wid->smallest_x + increment;
1677 additional_length -= increment;
1678 continue;
1679 }
1680 next_biggest_stepsize = std::max(next_biggest_stepsize, hor_step);
1681 }
1682 biggest_stepsize = next_biggest_stepsize;
1683
1684 if (num_changing_childs == 0 && flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1685 /* Second.5 loop: count how many children are of the updated biggest step size. */
1686 for (const auto &child_wid : this->children) {
1687 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1688 if (hor_step == biggest_stepsize) {
1689 num_changing_childs++;
1690 }
1691 }
1692 }
1693 }
1694 assert(num_changing_childs == 0);
1695
1696 uint pre = this->pip_pre;
1697 uint inter = this->pip_inter;
1698
1699 if (additional_length > 0) {
1700 /* Allocate remaining space by pip ratios. If this doesn't round exactly, the unused space will fall into pip_post
1701 * which is never explicitly needed. */
1702 int r = this->pip_ratio_pre + this->gaps * this->pip_ratio_inter + this->pip_ratio_post;
1703 if (r > 0) {
1704 pre += this->pip_ratio_pre * additional_length / r;
1705 if (this->gaps > 0) inter += this->pip_ratio_inter * additional_length / r;
1706 }
1707 }
1708
1709 /* Third loop: Compute position and call the child. */
1710 uint position = rtl ? this->current_x - pre : pre; // Place to put next child relative to origin of the container.
1711 for (const auto &child_wid : this->children) {
1712 uint child_width = child_wid->current_x;
1713 uint child_x = x + (rtl ? position - child_width - child_wid->padding.left : position + child_wid->padding.left);
1714 uint child_y = y + child_wid->padding.top;
1715
1716 child_wid->AssignSizePosition(sizing, child_x, child_y, child_width, child_wid->current_y, rtl);
1717 if (child_wid->current_x != 0) {
1718 uint padded_child_width = child_width + child_wid->padding.Horizontal() + inter;
1719 position = rtl ? position - padded_child_width : position + padded_child_width;
1720 }
1721 }
1722}
1723
1724void NWidgetHorizontalLTR::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool)
1725{
1726 NWidgetHorizontal::AssignSizePosition(sizing, x, y, given_width, given_height, false);
1727}
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
1915{
1916 /* Spacer widget is never normally visible. */
1917
1918 if (_draw_widget_outlines && this->current_x != 0 && this->current_y != 0) {
1919 /* Spacers indicate a potential design issue, so get extra highlighting. */
1920 GfxFillRect(this->GetCurrentRect(), PC_WHITE, FILLRECT_CHECKER);
1921
1922 DrawOutline(w, this);
1923 }
1924}
1925
1927{
1928 /* Spacer widget never need repainting. */
1929}
1930
1932{
1933 return nullptr;
1934}
1935
1941{
1942 this->clicked = clicked;
1943 if (this->clicked >= 0 && this->sb != nullptr && this->widgets_x != 0) {
1944 int vpos = (this->clicked / this->widgets_x) * this->widget_h; // Vertical position of the top.
1945 /* Need to scroll down -> Scroll to the bottom.
1946 * However, last entry has no 'this->pip_inter' underneath, and we must stay below this->sb->GetCount() */
1947 if (this->sb->GetPosition() < vpos) vpos += this->widget_h - this->pip_inter - 1;
1948 this->sb->ScrollTowards(vpos);
1949 }
1950}
1951
1958{
1959 this->count = count;
1960
1961 if (this->sb == nullptr || this->widgets_x == 0) return;
1962
1963 /* We need to get the number of pixels the matrix is high/wide.
1964 * So, determine the number of rows/columns based on the number of
1965 * columns/rows (one is constant/unscrollable).
1966 * Then multiply that by the height of a widget, and add the pre
1967 * and post spacing "offsets". */
1968 count = CeilDiv(count, this->sb->IsVertical() ? this->widgets_x : this->widgets_y);
1969 count *= (this->sb->IsVertical() ? this->children.front()->smallest_y : this->children.front()->smallest_x) + this->pip_inter;
1970 if (count > 0) count -= this->pip_inter; // We counted an inter too much in the multiplication above
1971 count += this->pip_pre + this->pip_post;
1972 this->sb->SetCount(count);
1973 this->sb->SetCapacity(this->sb->IsVertical() ? this->current_y : this->current_x);
1974 this->sb->SetStepSize(this->sb->IsVertical() ? this->widget_h : this->widget_w);
1975}
1976
1982{
1983 this->sb = sb;
1984}
1985
1991{
1992 return this->current_element;
1993}
1994
1996{
1997 assert(this->children.size() == 1);
1998
1999 this->children.front()->SetupSmallestSize(w);
2000
2001 Dimension padding = { (uint)this->pip_pre + this->pip_post, (uint)this->pip_pre + this->pip_post};
2002 Dimension size = {this->children.front()->smallest_x + padding.width, this->children.front()->smallest_y + padding.height};
2003 Dimension fill = {0, 0};
2004 Dimension resize = {this->pip_inter + this->children.front()->smallest_x, this->pip_inter + this->children.front()->smallest_y};
2005
2006 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
2007
2008 this->smallest_x = size.width;
2009 this->smallest_y = size.height;
2010 this->fill_x = fill.width;
2011 this->fill_y = fill.height;
2012 this->resize_x = resize.width;
2013 this->resize_y = resize.height;
2014 this->ApplyAspectRatio();
2015}
2016
2017void NWidgetMatrix::AssignSizePosition(SizingType, int x, int y, uint given_width, uint given_height, bool)
2018{
2019 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
2020
2021 this->pos_x = x;
2022 this->pos_y = y;
2023 this->current_x = given_width;
2024 this->current_y = given_height;
2025
2026 /* Determine the size of the widgets, and the number of visible widgets on each of the axis. */
2027 this->widget_w = this->children.front()->smallest_x + this->pip_inter;
2028 this->widget_h = this->children.front()->smallest_y + this->pip_inter;
2029
2030 /* Account for the pip_inter is between widgets, so we need to account for that when
2031 * the division assumes pip_inter is used for all widgets. */
2032 this->widgets_x = CeilDiv(this->current_x - this->pip_pre - this->pip_post + this->pip_inter, this->widget_w);
2033 this->widgets_y = CeilDiv(this->current_y - this->pip_pre - this->pip_post + this->pip_inter, this->widget_h);
2034
2035 /* When resizing, update the scrollbar's count. E.g. with a vertical
2036 * scrollbar becoming wider or narrower means the amount of rows in
2037 * the scrollbar becomes respectively smaller or higher. */
2038 this->SetCount(this->count);
2039}
2040
2042{
2043 /* Falls outside of the matrix widget. */
2044 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
2045
2046 int start_x, start_y, base_offs_x, base_offs_y;
2047 this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
2048
2049 bool rtl = _current_text_dir == TD_RTL;
2050
2051 int widget_col = (rtl ?
2052 -x + (int)this->pip_post + this->pos_x + base_offs_x + this->widget_w - 1 - (int)this->pip_inter :
2053 x - (int)this->pip_pre - this->pos_x - base_offs_x
2054 ) / this->widget_w;
2055
2056 int widget_row = (y - base_offs_y - (int)this->pip_pre - this->pos_y) / this->widget_h;
2057
2058 this->current_element = (widget_row + start_y) * this->widgets_x + start_x + widget_col;
2059 if (this->current_element >= this->count) return nullptr;
2060
2061 NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->children.front().get());
2062 assert(child != nullptr);
2064 this->pos_x + (rtl ? this->pip_post - widget_col * this->widget_w : this->pip_pre + widget_col * this->widget_w) + base_offs_x,
2065 this->pos_y + this->pip_pre + widget_row * this->widget_h + base_offs_y,
2066 child->smallest_x, child->smallest_y, rtl);
2067
2068 return child->GetWidgetFromPos(x, y);
2069}
2070
2071/* virtual */ void NWidgetMatrix::Draw(const Window *w)
2072{
2073 /* Fill the background. */
2074 GfxFillRect(this->GetCurrentRect(), GetColourGradient(this->colour, SHADE_LIGHT));
2075
2076 /* Set up a clipping area for the previews. */
2077 bool rtl = _current_text_dir == TD_RTL;
2078 DrawPixelInfo tmp_dpi;
2079 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;
2080
2081 {
2082 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
2083
2084 /* Get the appropriate offsets so we can draw the right widgets. */
2085 NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->children.front().get());
2086 assert(child != nullptr);
2087 int start_x, start_y, base_offs_x, base_offs_y;
2088 this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
2089
2090 int offs_y = base_offs_y;
2091 for (int y = start_y; y < start_y + this->widgets_y + 1; y++, offs_y += this->widget_h) {
2092 /* Are we within bounds? */
2093 if (offs_y + child->smallest_y <= 0) continue;
2094 if (offs_y >= (int)this->current_y) break;
2095
2096 /* We've passed our amount of widgets. */
2097 if (y * this->widgets_x >= this->count) break;
2098
2099 int offs_x = base_offs_x;
2100 for (int x = start_x; x < start_x + this->widgets_x + 1; x++, offs_x += rtl ? -this->widget_w : this->widget_w) {
2101 /* Are we within bounds? */
2102 if (offs_x + child->smallest_x <= 0) continue;
2103 if (offs_x >= (int)this->current_x) continue;
2104
2105 /* Do we have this many widgets? */
2106 this->current_element = y * this->widgets_x + x;
2107 if (this->current_element >= this->count) break;
2108
2109 child->AssignSizePosition(ST_RESIZE, offs_x, offs_y, child->smallest_x, child->smallest_y, rtl);
2110 child->SetLowered(this->clicked == this->current_element);
2111 child->Draw(w);
2112 }
2113 }
2114 }
2115
2116 DrawOutline(w, this);
2117}
2118
2126void NWidgetMatrix::GetScrollOffsets(int &start_x, int &start_y, int &base_offs_x, int &base_offs_y)
2127{
2128 base_offs_x = _current_text_dir == TD_RTL ? this->widget_w * (this->widgets_x - 1) : 0;
2129 base_offs_y = 0;
2130 start_x = 0;
2131 start_y = 0;
2132 if (this->sb != nullptr) {
2133 if (this->sb->IsVertical()) {
2134 start_y = this->sb->GetPosition() / this->widget_h;
2135 base_offs_y += -this->sb->GetPosition() + start_y * this->widget_h;
2136 } else {
2137 start_x = this->sb->GetPosition() / this->widget_w;
2138 int sub_x = this->sb->GetPosition() - start_x * this->widget_w;
2139 if (_current_text_dir == TD_RTL) {
2140 base_offs_x += sub_x;
2141 } else {
2142 base_offs_x -= sub_x;
2143 }
2144 }
2145 }
2146}
2147
2157NWidgetBackground::NWidgetBackground(WidgetType tp, Colours colour, WidgetID index, std::unique_ptr<NWidgetPIPContainer> &&child) : NWidgetCore(tp, colour, index, 1, 1, {}, STR_NULL)
2158{
2159 assert(tp == WWT_PANEL || tp == WWT_INSET || tp == WWT_FRAME);
2160 this->child = std::move(child);
2161 if (this->child != nullptr) this->child->parent = this;
2162 this->SetAlignment(SA_TOP | SA_LEFT);
2163}
2164
2172void NWidgetBackground::Add(std::unique_ptr<NWidgetBase> &&nwid)
2173{
2174 if (this->child == nullptr) {
2175 this->child = std::make_unique<NWidgetVertical>();
2176 }
2177 nwid->parent = this->child.get();
2178 this->child->Add(std::move(nwid));
2179}
2180
2191void NWidgetBackground::SetPIP(uint8_t pip_pre, uint8_t pip_inter, uint8_t pip_post)
2192{
2193 if (this->child == nullptr) {
2194 this->child = std::make_unique<NWidgetVertical>();
2195 }
2196 this->child->parent = this;
2197 this->child->SetPIP(pip_pre, pip_inter, pip_post);
2198}
2199
2210void NWidgetBackground::SetPIPRatio(uint8_t pip_ratio_pre, uint8_t pip_ratio_inter, uint8_t pip_ratio_post)
2211{
2212 if (this->child == nullptr) {
2213 this->child = std::make_unique<NWidgetVertical>();
2214 }
2215 this->child->parent = this;
2216 this->child->SetPIPRatio(pip_ratio_pre, pip_ratio_inter, pip_ratio_post);
2217}
2218
2219void NWidgetBackground::AdjustPaddingForZoom()
2220{
2221 if (child != nullptr) child->AdjustPaddingForZoom();
2222 NWidgetCore::AdjustPaddingForZoom();
2223}
2224
2226{
2227 if (this->child != nullptr) {
2228 this->child->SetupSmallestSize(w);
2229
2230 this->smallest_x = this->child->smallest_x;
2231 this->smallest_y = this->child->smallest_y;
2232 this->fill_x = this->child->fill_x;
2233 this->fill_y = this->child->fill_y;
2234 this->resize_x = this->child->resize_x;
2235 this->resize_y = this->child->resize_y;
2236
2237 /* Don't apply automatic padding if there is no child widget. */
2238 if (w == nullptr) return;
2239
2240 if (this->type == WWT_FRAME) {
2241 std::string text = GetStringForWidget(w, this);
2242 Dimension text_size = text.empty() ? Dimension{0, 0} : GetStringBoundingBox(text, this->text_size);
2243
2244 /* Account for the size of the frame's text if that exists */
2245 this->child->padding = WidgetDimensions::scaled.frametext;
2246 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);
2247
2248 this->smallest_x += this->child->padding.Horizontal();
2249 this->smallest_y += this->child->padding.Vertical();
2250
2251 this->smallest_x = std::max(this->smallest_x, text_size.width + WidgetDimensions::scaled.frametext.Horizontal());
2252 } else if (this->type == WWT_INSET) {
2253 /* Apply automatic padding for bevel thickness. */
2254 this->child->padding = WidgetDimensions::scaled.bevel;
2255
2256 this->smallest_x += this->child->padding.Horizontal();
2257 this->smallest_y += this->child->padding.Vertical();
2258 }
2259 this->ApplyAspectRatio();
2260 } else {
2261 Dimension d = {this->min_x, this->min_y};
2262 Dimension fill = {this->fill_x, this->fill_y};
2263 Dimension resize = {this->resize_x, this->resize_y};
2264 if (w != nullptr) { // A non-nullptr window pointer acts as switch to turn dynamic widget size on.
2265 if (this->type == WWT_FRAME || this->type == WWT_INSET) {
2266 std::string text = GetStringForWidget(w, this);
2267 if (!text.empty()) {
2268 Dimension background = GetStringBoundingBox(text, this->text_size);
2269 background.width += (this->type == WWT_FRAME) ? (WidgetDimensions::scaled.frametext.Horizontal()) : (WidgetDimensions::scaled.inset.Horizontal());
2270 d = maxdim(d, background);
2271 }
2272 }
2273 if (this->index >= 0) {
2275 switch (this->type) {
2276 default: NOT_REACHED();
2280 }
2281 w->UpdateWidgetSize(this->index, d, padding, fill, resize);
2282 }
2283 }
2284 this->smallest_x = d.width;
2285 this->smallest_y = d.height;
2286 this->fill_x = fill.width;
2287 this->fill_y = fill.height;
2288 this->resize_x = resize.width;
2289 this->resize_y = resize.height;
2290 this->ApplyAspectRatio();
2291 }
2292}
2293
2294void NWidgetBackground::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
2295{
2296 this->StoreSizePosition(sizing, x, y, given_width, given_height);
2297
2298 if (this->child != nullptr) {
2299 uint x_offset = (rtl ? this->child->padding.right : this->child->padding.left);
2300 uint width = given_width - this->child->padding.Horizontal();
2301 uint height = given_height - this->child->padding.Vertical();
2302 this->child->AssignSizePosition(sizing, x + x_offset, y + this->child->padding.top, width, height, rtl);
2303 }
2304}
2305
2307{
2308 this->NWidgetCore::FillWidgetLookup(widget_lookup);
2309 if (this->child != nullptr) this->child->FillWidgetLookup(widget_lookup);
2310}
2311
2313{
2314 if (this->current_x == 0 || this->current_y == 0) return;
2315
2316 Rect r = this->GetCurrentRect();
2317
2318 const DrawPixelInfo *dpi = _cur_dpi;
2319 if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2320
2321 switch (this->type) {
2322 case WWT_PANEL:
2323 DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, this->IsLowered() ? FrameFlag::Lowered : FrameFlags{});
2324 break;
2325
2326 case WWT_FRAME:
2327 DrawFrame(r, this->colour, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
2328 break;
2329
2330 case WWT_INSET:
2331 DrawInset(r, this->colour, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
2332 break;
2333
2334 default:
2335 NOT_REACHED();
2336 }
2337
2338 if (this->index >= 0) w->DrawWidget(r, this->index);
2339 if (this->child != nullptr) this->child->Draw(w);
2340
2341 if (this->IsDisabled()) {
2343 }
2344
2345 DrawOutline(w, this);
2346}
2347
2349{
2350 NWidgetCore *nwid = nullptr;
2351 if (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) {
2352 if (this->child != nullptr) nwid = this->child->GetWidgetFromPos(x, y);
2353 if (nwid == nullptr) nwid = this;
2354 }
2355 return nwid;
2356}
2357
2359{
2360 NWidgetBase *nwid = nullptr;
2361 if (this->child != nullptr) nwid = this->child->GetWidgetOfType(tp);
2362 if (nwid == nullptr && this->type == tp) nwid = this;
2363 return nwid;
2364}
2365
2366NWidgetViewport::NWidgetViewport(WidgetID index) : NWidgetCore(NWID_VIEWPORT, INVALID_COLOUR, index, 1, 1, {}, STR_NULL)
2367{
2368}
2369
2371{
2372 this->smallest_x = this->min_x;
2373 this->smallest_y = this->min_y;
2374 this->ApplyAspectRatio();
2375}
2376
2378{
2379 if (this->current_x == 0 || this->current_y == 0) return;
2380
2383 _transparency_opt &= (1 << TO_SIGNS) | (1 << TO_TEXT); // Disable all transparency, except textual stuff
2384 w->DrawViewport();
2385 _transparency_opt = to_backup;
2386 } else {
2387 w->DrawViewport();
2388 }
2389
2390 /* Optionally shade the viewport. */
2391 if (this->disp_flags.Any({NWidgetDisplayFlag::ShadeGrey, NWidgetDisplayFlag::ShadeDimmed})) {
2393 }
2394
2395 DrawOutline(w, this);
2396}
2397
2404void NWidgetViewport::InitializeViewport(Window *w, std::variant<TileIndex, VehicleID> focus, ZoomLevel zoom)
2405{
2406 InitializeWindowViewport(w, this->pos_x, this->pos_y, this->current_x, this->current_y, focus, zoom);
2407}
2408
2414{
2415 if (w->viewport == nullptr) return;
2416
2417 Viewport &vp = *w->viewport;
2418 vp.left = w->left + this->pos_x;
2419 vp.top = w->top + this->pos_y;
2420 vp.width = this->current_x;
2421 vp.height = this->current_y;
2422
2423 vp.virtual_width = ScaleByZoom(vp.width, vp.zoom);
2425}
2426
2436Scrollbar::size_type Scrollbar::GetScrolledRowFromWidget(int clickpos, const Window * const w, WidgetID widget, int padding, int line_height) const
2437{
2438 int pos = w->GetRowFromWidget(clickpos, widget, padding, line_height);
2439 if (pos != INT_MAX) pos += this->GetPosition();
2440 return (pos < 0 || pos >= this->GetCount()) ? Scrollbar::npos : pos;
2441}
2442
2457EventState Scrollbar::UpdateListPositionOnKeyPress(int &list_position, uint16_t keycode) const
2458{
2459 int new_pos = list_position;
2460 switch (keycode) {
2461 case WKC_UP:
2462 /* scroll up by one */
2463 new_pos--;
2464 break;
2465
2466 case WKC_DOWN:
2467 /* scroll down by one */
2468 new_pos++;
2469 break;
2470
2471 case WKC_PAGEUP:
2472 /* scroll up a page */
2473 new_pos -= this->GetCapacity();
2474 break;
2475
2476 case WKC_PAGEDOWN:
2477 /* scroll down a page */
2478 new_pos += this->GetCapacity();
2479 break;
2480
2481 case WKC_HOME:
2482 /* jump to beginning */
2483 new_pos = 0;
2484 break;
2485
2486 case WKC_END:
2487 /* jump to end */
2488 new_pos = this->GetCount() - 1;
2489 break;
2490
2491 default:
2492 return ES_NOT_HANDLED;
2493 }
2494
2495 /* If there are no elements, there is nothing to scroll/update. */
2496 if (this->GetCount() != 0) {
2497 list_position = Clamp(new_pos, 0, this->GetCount() - 1);
2498 }
2499 return ES_HANDLED;
2500}
2501
2502
2511{
2512 NWidgetBase *nwid = w->GetWidget<NWidgetBase>(widget);
2513 if (this->IsVertical()) {
2514 this->SetCapacity(((int)nwid->current_y - padding) / (int)nwid->resize_y);
2515 } else {
2516 this->SetCapacity(((int)nwid->current_x - padding) / (int)nwid->resize_x);
2517 }
2518}
2519
2527Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
2528{
2529 const int count = sb.GetCount() * resize_step;
2530 const int position = sb.GetPosition() * resize_step;
2531
2532 if (sb.IsVertical()) {
2533 r.top -= position;
2534 r.bottom = r.top + count;
2535 } else {
2536 bool rtl = _current_text_dir == TD_RTL;
2537 if (rtl) {
2538 r.right += position;
2539 r.left = r.right - count;
2540 } else {
2541 r.left -= position;
2542 r.right = r.left + count;
2543 }
2544 }
2545
2546 return r;
2547}
2548
2555NWidgetScrollbar::NWidgetScrollbar(WidgetType tp, Colours colour, WidgetID index) : NWidgetCore(tp, colour, index, 1, 1, {}, STR_NULL), Scrollbar(tp != NWID_HSCROLLBAR)
2556{
2557 assert(tp == NWID_HSCROLLBAR || tp == NWID_VSCROLLBAR);
2558
2559 switch (this->type) {
2560 case NWID_HSCROLLBAR:
2561 this->SetResize(1, 0);
2562 this->SetFill(1, 0);
2563 this->SetToolTip(STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST);
2564 break;
2565
2566 case NWID_VSCROLLBAR:
2567 this->SetResize(0, 1);
2568 this->SetFill(0, 1);
2569 this->SetToolTip(STR_TOOLTIP_VSCROLL_BAR_SCROLLS_LIST);
2570 break;
2571
2572 default: NOT_REACHED();
2573 }
2574}
2575
2577{
2578 this->min_x = 0;
2579 this->min_y = 0;
2580
2581 switch (this->type) {
2582 case NWID_HSCROLLBAR:
2583 this->SetMinimalSizeAbsolute(NWidgetScrollbar::GetHorizontalDimension().width * 3, NWidgetScrollbar::GetHorizontalDimension().height);
2584 break;
2585
2586 case NWID_VSCROLLBAR:
2587 this->SetMinimalSizeAbsolute(NWidgetScrollbar::GetVerticalDimension().width, NWidgetScrollbar::GetVerticalDimension().height * 3);
2588 break;
2589
2590 default: NOT_REACHED();
2591 }
2592
2593 this->smallest_x = this->min_x;
2594 this->smallest_y = this->min_y;
2595}
2596
2598{
2599 if (this->current_x == 0 || this->current_y == 0) return;
2600
2601 Rect r = this->GetCurrentRect();
2602
2603 const DrawPixelInfo *dpi = _cur_dpi;
2604 if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2605
2606 bool up_lowered = this->disp_flags.Test(NWidgetDisplayFlag::ScrollbarUp);
2607 bool down_lowered = this->disp_flags.Test(NWidgetDisplayFlag::ScrollbarDown);
2608 bool middle_lowered = !this->disp_flags.Any({NWidgetDisplayFlag::ScrollbarUp, NWidgetDisplayFlag::ScrollbarDown}) && w->mouse_capture_widget == this->index;
2609
2610 if (this->type == NWID_HSCROLLBAR) {
2611 DrawHorizontalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2612 } else {
2613 DrawVerticalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2614 }
2615
2616 if (this->IsDisabled()) {
2618 }
2619
2620 DrawOutline(w, this);
2621}
2622
2623/* static */ void NWidgetScrollbar::InvalidateDimensionCache()
2624{
2625 vertical_dimension.width = vertical_dimension.height = 0;
2626 horizontal_dimension.width = horizontal_dimension.height = 0;
2627}
2628
2629/* static */ Dimension NWidgetScrollbar::GetVerticalDimension()
2630{
2631 if (vertical_dimension.width == 0) {
2632 vertical_dimension = maxdim(GetScaledSpriteSize(SPR_ARROW_UP), GetScaledSpriteSize(SPR_ARROW_DOWN));
2635 }
2636 return vertical_dimension;
2637}
2638
2639/* static */ Dimension NWidgetScrollbar::GetHorizontalDimension()
2640{
2641 if (horizontal_dimension.width == 0) {
2642 horizontal_dimension = maxdim(GetScaledSpriteSize(SPR_ARROW_LEFT), GetScaledSpriteSize(SPR_ARROW_RIGHT));
2645 }
2646 return horizontal_dimension;
2647}
2648
2651
2654{
2655 shadebox_dimension.width = shadebox_dimension.height = 0;
2656 debugbox_dimension.width = debugbox_dimension.height = 0;
2657 defsizebox_dimension.width = defsizebox_dimension.height = 0;
2658 stickybox_dimension.width = stickybox_dimension.height = 0;
2659 resizebox_dimension.width = resizebox_dimension.height = 0;
2660 closebox_dimension.width = closebox_dimension.height = 0;
2661 dropdown_dimension.width = dropdown_dimension.height = 0;
2662}
2663
2671
2680NWidgetLeaf::NWidgetLeaf(WidgetType tp, Colours colour, WidgetID index, const WidgetData &data, StringID tip) : NWidgetCore(tp, colour, index, 1, 1, data, tip)
2681{
2682 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);
2683 this->min_x = 0;
2684 this->min_y = 0;
2685 this->SetResize(0, 0);
2686
2687 switch (tp) {
2688 case WWT_EMPTY:
2689 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_EMPTY should not have a colour");
2690 break;
2691
2692 case WWT_TEXT:
2693 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_TEXT should not have a colour");
2694 this->SetFill(0, 0);
2696 break;
2697
2698 case WWT_LABEL:
2699 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_LABEL should not have a colour");
2700 [[fallthrough]];
2701
2702 case WWT_PUSHBTN:
2703 case WWT_IMGBTN:
2704 case WWT_PUSHIMGBTN:
2705 case WWT_IMGBTN_2:
2706 case WWT_TEXTBTN:
2707 case WWT_PUSHTXTBTN:
2708 case WWT_TEXTBTN_2:
2709 case WWT_IMGTEXTBTN:
2710 case WWT_PUSHIMGTEXTBTN:
2711 case WWT_BOOLBTN:
2712 case WWT_MATRIX:
2714 case NWID_PUSHBUTTON_DROPDOWN:
2715 this->SetFill(0, 0);
2716 break;
2717
2718 case WWT_ARROWBTN:
2719 case WWT_PUSHARROWBTN:
2720 this->SetFill(0, 0);
2721 this->SetAspect(WidgetDimensions::ASPECT_LEFT_RIGHT_BUTTON);
2722 break;
2723
2724 case WWT_EDITBOX:
2725 this->SetFill(0, 0);
2726 break;
2727
2728 case WWT_CAPTION:
2729 this->SetFill(1, 0);
2730 this->SetResize(1, 0);
2732 this->SetMinimalTextLines(1, WidgetDimensions::unscaled.captiontext.Vertical(), FS_NORMAL);
2733 this->SetToolTip(STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
2734 break;
2735
2736 case WWT_STICKYBOX:
2737 this->SetFill(0, 0);
2739 this->SetToolTip(STR_TOOLTIP_STICKY);
2740 this->SetAspect(this->min_x, this->min_y);
2741 break;
2742
2743 case WWT_SHADEBOX:
2744 this->SetFill(0, 0);
2746 this->SetToolTip(STR_TOOLTIP_SHADE);
2747 this->SetAspect(this->min_x, this->min_y);
2748 break;
2749
2750 case WWT_DEBUGBOX:
2751 this->SetFill(0, 0);
2753 this->SetToolTip(STR_TOOLTIP_DEBUG);
2754 this->SetAspect(this->min_x, this->min_y);
2755 break;
2756
2757 case WWT_DEFSIZEBOX:
2758 this->SetFill(0, 0);
2760 this->SetToolTip(STR_TOOLTIP_DEFSIZE);
2761 this->SetAspect(this->min_x, this->min_y);
2762 break;
2763
2764 case WWT_RESIZEBOX:
2765 this->SetFill(0, 0);
2768 this->SetToolTip(STR_TOOLTIP_RESIZE);
2769 break;
2770
2771 case WWT_CLOSEBOX:
2772 this->SetFill(0, 0);
2774 this->SetToolTip(STR_TOOLTIP_CLOSE_WINDOW);
2775 this->SetAspect(this->min_x, this->min_y);
2776 break;
2777
2778 case WWT_DROPDOWN:
2779 this->SetFill(0, 0);
2781 this->SetAlignment(SA_TOP | SA_LEFT);
2782 break;
2783
2784 default:
2785 NOT_REACHED();
2786 }
2787}
2788
2790{
2791 Dimension padding = {0, 0};
2792 Dimension size = {this->min_x, this->min_y};
2793 Dimension fill = {this->fill_x, this->fill_y};
2794 Dimension resize = {this->resize_x, this->resize_y};
2795 switch (this->type) {
2796 case WWT_EMPTY: {
2797 break;
2798 }
2799 case WWT_MATRIX: {
2801 break;
2802 }
2803 case WWT_SHADEBOX: {
2805 if (NWidgetLeaf::shadebox_dimension.width == 0) {
2806 NWidgetLeaf::shadebox_dimension = maxdim(GetScaledSpriteSize(SPR_WINDOW_SHADE), GetScaledSpriteSize(SPR_WINDOW_UNSHADE));
2809 }
2811 break;
2812 }
2813 case WWT_DEBUGBOX:
2816 if (NWidgetLeaf::debugbox_dimension.width == 0) {
2820 }
2822 } else {
2823 /* If the setting is disabled we don't want to see it! */
2824 size.width = 0;
2825 fill.width = 0;
2826 resize.width = 0;
2827 }
2828 break;
2829
2830 case WWT_STICKYBOX: {
2832 if (NWidgetLeaf::stickybox_dimension.width == 0) {
2836 }
2838 break;
2839 }
2840
2841 case WWT_DEFSIZEBOX: {
2843 if (NWidgetLeaf::defsizebox_dimension.width == 0) {
2847 }
2849 break;
2850 }
2851
2852 case WWT_RESIZEBOX: {
2854 if (NWidgetLeaf::resizebox_dimension.width == 0) {
2855 NWidgetLeaf::resizebox_dimension = maxdim(GetScaledSpriteSize(SPR_WINDOW_RESIZE_LEFT), GetScaledSpriteSize(SPR_WINDOW_RESIZE_RIGHT));
2858 }
2860 break;
2861 }
2862 case WWT_EDITBOX: {
2863 Dimension sprite_size = GetScaledSpriteSize(_current_text_dir == TD_RTL ? SPR_IMG_DELETE_RIGHT : SPR_IMG_DELETE_LEFT);
2864 size.width = std::max(size.width, ScaleGUITrad(30) + sprite_size.width);
2865 size.height = std::max(sprite_size.height, GetStringBoundingBox("_").height + WidgetDimensions::scaled.framerect.Vertical());
2866 }
2867 [[fallthrough]];
2868 case WWT_PUSHBTN: {
2870 break;
2871 }
2872
2873 case WWT_BOOLBTN:
2874 size.width = SETTING_BUTTON_WIDTH;
2875 size.height = SETTING_BUTTON_HEIGHT;
2876 break;
2877
2878 case WWT_IMGBTN:
2879 case WWT_IMGBTN_2:
2880 case WWT_PUSHIMGBTN: {
2882 Dimension d2 = GetScaledSpriteSize(this->widget_data.sprite);
2883 if (this->type == WWT_IMGBTN_2) d2 = maxdim(d2, GetScaledSpriteSize(this->widget_data.sprite + 1));
2884 d2.width += padding.width;
2885 d2.height += padding.height;
2886 size = maxdim(size, d2);
2887 break;
2888 }
2889
2890 case WWT_IMGTEXTBTN:
2891 case WWT_PUSHIMGTEXTBTN: {
2893 Dimension di = GetScaledSpriteSize(this->widget_data.sprite);
2894 Dimension dt = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2895 Dimension d2{
2896 padding.width + di.width + WidgetDimensions::scaled.hsep_wide + dt.width,
2897 padding.height + std::max(di.height, dt.height)
2898 };
2899 size = maxdim(size, d2);
2900 break;
2901 }
2902
2903 case WWT_ARROWBTN:
2904 case WWT_PUSHARROWBTN: {
2906 Dimension d2 = maxdim(GetScaledSpriteSize(SPR_ARROW_LEFT), GetScaledSpriteSize(SPR_ARROW_RIGHT));
2907 d2.width += padding.width;
2908 d2.height += padding.height;
2909 size = maxdim(size, d2);
2910 break;
2911 }
2912
2913 case WWT_CLOSEBOX: {
2915 if (NWidgetLeaf::closebox_dimension.width == 0) {
2919 }
2921 break;
2922 }
2923 case WWT_TEXTBTN:
2924 case WWT_PUSHTXTBTN:
2925 case WWT_TEXTBTN_2: {
2927 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2928 d2.width += padding.width;
2929 d2.height += padding.height;
2930 size = maxdim(size, d2);
2931 break;
2932 }
2933 case WWT_LABEL:
2934 case WWT_TEXT: {
2935 size = maxdim(size, GetStringBoundingBox(GetStringForWidget(w, this), this->text_size));
2936 break;
2937 }
2938 case WWT_CAPTION: {
2940 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2941 d2.width += padding.width;
2942 d2.height += padding.height;
2943 size = maxdim(size, d2);
2944 break;
2945 }
2946 case WWT_DROPDOWN:
2948 case NWID_PUSHBUTTON_DROPDOWN: {
2949 if (NWidgetLeaf::dropdown_dimension.width == 0) {
2953 }
2955 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2956 d2.width += padding.width;
2957 d2.height = std::max(d2.height + padding.height, NWidgetLeaf::dropdown_dimension.height);
2958 size = maxdim(size, d2);
2959 break;
2960 }
2961 default:
2962 NOT_REACHED();
2963 }
2964
2965 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
2966
2967 this->smallest_x = size.width;
2968 this->smallest_y = size.height;
2969 this->fill_x = fill.width;
2970 this->fill_y = fill.height;
2971 this->resize_x = resize.width;
2972 this->resize_y = resize.height;
2973 this->ApplyAspectRatio();
2974}
2975
2977{
2978 if (this->current_x == 0 || this->current_y == 0) return;
2979
2980 /* Setup a clipping rectangle... for WWT_EMPTY or WWT_TEXT, an extra scaled pixel is allowed in case text shadow encroaches. */
2981 int extra = (this->type == WWT_EMPTY || this->type == WWT_TEXT) ? ScaleGUITrad(1) : 0;
2982 DrawPixelInfo new_dpi;
2983 if (!FillDrawPixelInfo(&new_dpi, this->pos_x, this->pos_y, this->current_x + extra, this->current_y + extra)) return;
2984 /* ...but keep coordinates relative to the window. */
2985 new_dpi.left += this->pos_x;
2986 new_dpi.top += this->pos_y;
2987
2988 AutoRestoreBackup dpi_backup(_cur_dpi, &new_dpi);
2989
2990 Rect r = this->GetCurrentRect();
2991
2992 bool clicked = this->IsLowered();
2993 switch (this->type) {
2994 case WWT_EMPTY:
2995 /* WWT_EMPTY used as a spacer indicates a potential design issue. */
2996 if (this->index == -1 && _draw_widget_outlines) {
2998 }
2999 break;
3000
3001 case WWT_PUSHBTN:
3002 DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
3003 break;
3004
3005 case WWT_BOOLBTN: {
3007 Colours button_colour = this->widget_data.alternate_colour;
3008 if (button_colour == INVALID_COLOUR) button_colour = this->colour;
3009 DrawBoolButton(pt.x, pt.y, button_colour, this->colour, clicked, !this->IsDisabled());
3010 break;
3011 }
3012
3013 case WWT_IMGBTN:
3014 case WWT_PUSHIMGBTN:
3015 case WWT_IMGBTN_2:
3016 DrawImageButtons(r, this->type, this->colour, clicked, this->widget_data.sprite, this->align);
3017 break;
3018
3019 case WWT_TEXTBTN:
3020 case WWT_PUSHTXTBTN:
3021 case WWT_TEXTBTN_2:
3022 DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
3023 DrawLabel(r, this->text_colour, GetStringForWidget(w, this, (type & WWT_MASK) == WWT_TEXTBTN_2 && clicked), this->align, this->text_size);
3024 break;
3025
3026 case WWT_IMGTEXTBTN:
3027 case WWT_PUSHIMGTEXTBTN:
3028 DrawImageTextButtons(r, this->colour, clicked, this->widget_data.sprite, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3029 break;
3030
3031 case WWT_ARROWBTN:
3032 case WWT_PUSHARROWBTN: {
3033 SpriteID sprite;
3034 switch (this->widget_data.arrow_widget_type) {
3035 case AWV_DECREASE: sprite = _current_text_dir != TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
3036 case AWV_INCREASE: sprite = _current_text_dir == TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
3037 case AWV_LEFT: sprite = SPR_ARROW_LEFT; break;
3038 case AWV_RIGHT: sprite = SPR_ARROW_RIGHT; break;
3039 default: NOT_REACHED();
3040 }
3041 DrawImageButtons(r, WWT_PUSHIMGBTN, this->colour, clicked, sprite, this->align);
3042 break;
3043 }
3044
3045 case WWT_LABEL:
3046 DrawLabel(r, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3047 break;
3048
3049 case WWT_TEXT:
3050 DrawText(r, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3051 break;
3052
3053 case WWT_MATRIX:
3054 DrawMatrix(r, this->colour, clicked, this->widget_data.matrix.width, this->widget_data.matrix.height, this->resize_x, this->resize_y);
3055 break;
3056
3057 case WWT_EDITBOX: {
3058 const QueryString *query = w->GetQueryString(this->index);
3059 if (query != nullptr) query->DrawEditBox(w, this->index);
3060 break;
3061 }
3062
3063 case WWT_CAPTION:
3064 DrawCaption(r, this->colour, w->owner, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3065 break;
3066
3067 case WWT_SHADEBOX:
3068 DrawShadeBox(r, this->colour, w->IsShaded());
3069 break;
3070
3071 case WWT_DEBUGBOX:
3072 DrawDebugBox(r, this->colour, clicked);
3073 break;
3074
3075 case WWT_STICKYBOX:
3077 break;
3078
3079 case WWT_DEFSIZEBOX:
3080 DrawDefSizeBox(r, this->colour, clicked);
3081 break;
3082
3083 case WWT_RESIZEBOX:
3084 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);
3085 break;
3086
3087 case WWT_CLOSEBOX:
3088 DrawCloseBox(r, this->colour);
3089 break;
3090
3091 case WWT_DROPDOWN:
3092 DrawButtonDropdown(r, this->colour, false, clicked, GetStringForWidget(w, this), this->align);
3093 break;
3094
3096 case NWID_PUSHBUTTON_DROPDOWN:
3097 DrawButtonDropdown(r, this->colour, clicked, this->disp_flags.Test(NWidgetDisplayFlag::DropdownActive), GetStringForWidget(w, this), this->align);
3098 break;
3099
3100 default:
3101 NOT_REACHED();
3102 }
3103 if (this->index >= 0) w->DrawWidget(r, this->index);
3104
3105 if (this->IsDisabled() && this->type != WWT_BOOLBTN) {
3106 /* WWT_BOOLBTN is excluded as it draws its own disabled state. */
3108 }
3109
3110 DrawOutline(w, this);
3111}
3112
3121{
3122 if (_current_text_dir == TD_LTR) {
3123 int button_width = this->pos_x + this->current_x - NWidgetLeaf::dropdown_dimension.width;
3124 return pt.x < button_width;
3125 } else {
3126 int button_left = this->pos_x + NWidgetLeaf::dropdown_dimension.width;
3127 return pt.x >= button_left;
3128 }
3129}
3130
3131/* == Conversion code from NWidgetPart array to NWidgetBase* tree == */
3132
3139{
3140 return tp > WPT_ATTRIBUTE_BEGIN && tp < WPT_ATTRIBUTE_END;
3141}
3142
3150{
3151 switch (nwid.type) {
3152 case WPT_RESIZE: {
3153 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3154 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_RESIZE requires NWidgetResizeBase");
3155 assert(nwid.u.xy.x >= 0 && nwid.u.xy.y >= 0);
3156 nwrb->SetResize(nwid.u.xy.x, nwid.u.xy.y);
3157 break;
3158 }
3159
3160 case WPT_MINSIZE: {
3161 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3162 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_MINSIZE requires NWidgetResizeBase");
3163 assert(nwid.u.xy.x >= 0 && nwid.u.xy.y >= 0);
3164 nwrb->SetMinimalSize(nwid.u.xy.x, nwid.u.xy.y);
3165 break;
3166 }
3167
3168 case WPT_MINTEXTLINES: {
3169 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3170 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_MINTEXTLINES requires NWidgetResizeBase");
3171 assert(nwid.u.text_lines.size >= FS_BEGIN && nwid.u.text_lines.size < FS_END);
3173 break;
3174 }
3175
3176 case WPT_TEXTSTYLE: {
3177 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3178 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_TEXTSTYLE requires NWidgetCore");
3179 nwc->SetTextStyle(nwid.u.text_style.colour, nwid.u.text_style.size);
3180 break;
3181 }
3182
3183 case WPT_ALIGNMENT: {
3184 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3185 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_ALIGNMENT requires NWidgetCore");
3186 nwc->SetAlignment(nwid.u.align.align);
3187 break;
3188 }
3189
3190 case WPT_FILL: {
3191 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3192 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_FILL requires NWidgetResizeBase");
3193 nwrb->SetFill(nwid.u.xy.x, nwid.u.xy.y);
3194 break;
3195 }
3196
3197 case WPT_DATATIP: {
3198 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3199 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_DATATIP requires NWidgetCore");
3200 nwc->widget_data = nwid.u.data_tip.data;
3201 nwc->SetToolTip(nwid.u.data_tip.tooltip);
3202 break;
3203 }
3204
3205 case WPT_PADDING:
3206 if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_PADDING requires NWidgetBase");
3207 dest->SetPadding(nwid.u.padding);
3208 break;
3209
3210 case WPT_PIPSPACE: {
3211 NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(dest);
3212 if (nwc != nullptr) nwc->SetPIP(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3213
3214 NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(dest);
3215 if (nwb != nullptr) nwb->SetPIP(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3216
3217 if (nwc == nullptr && nwb == nullptr) [[unlikely]] throw std::runtime_error("WPT_PIPSPACE requires NWidgetPIPContainer or NWidgetBackground");
3218 break;
3219 }
3220
3221 case WPT_PIPRATIO: {
3222 NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(dest);
3223 if (nwc != nullptr) nwc->SetPIPRatio(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3224
3225 NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(dest);
3226 if (nwb != nullptr) nwb->SetPIPRatio(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3227
3228 if (nwc == nullptr && nwb == nullptr) [[unlikely]] throw std::runtime_error("WPT_PIPRATIO requires NWidgetPIPContainer or NWidgetBackground");
3229 break;
3230 }
3231
3232 case WPT_SCROLLBAR: {
3233 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3234 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_SCROLLBAR requires NWidgetCore");
3235 nwc->scrollbar_index = nwid.u.widget.index;
3236 break;
3237 }
3238
3239 case WPT_ASPECT: {
3240 if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_ASPECT requires NWidgetBase");
3241 dest->aspect_ratio = nwid.u.aspect.ratio;
3242 dest->aspect_flags = nwid.u.aspect.flags;
3243 break;
3244 }
3245
3246 default:
3247 NOT_REACHED();
3248 }
3249}
3250
3257static std::unique_ptr<NWidgetBase> MakeNWidget(const NWidgetPart &nwid)
3258{
3259 assert(!IsAttributeWidgetPartType(nwid.type));
3260 assert(nwid.type != WPT_ENDCONTAINER);
3261
3262 switch (nwid.type) {
3263 case NWID_SPACER: return std::make_unique<NWidgetSpacer>(0, 0);
3264
3265 case WWT_PANEL: [[fallthrough]];
3266 case WWT_INSET: [[fallthrough]];
3267 case WWT_FRAME: return std::make_unique<NWidgetBackground>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index);
3268
3269 case NWID_HORIZONTAL: return std::make_unique<NWidgetHorizontal>(nwid.u.container.flags, nwid.u.container.index);
3270 case NWID_HORIZONTAL_LTR: return std::make_unique<NWidgetHorizontalLTR>(nwid.u.container.flags, nwid.u.container.index);
3271 case NWID_VERTICAL: return std::make_unique<NWidgetVertical>(nwid.u.container.flags, nwid.u.container.index);
3272 case NWID_SELECTION: return std::make_unique<NWidgetStacked>(nwid.u.widget.index);
3273 case NWID_MATRIX: return std::make_unique<NWidgetMatrix>(nwid.u.widget.colour, nwid.u.widget.index);
3274 case NWID_VIEWPORT: return std::make_unique<NWidgetViewport>(nwid.u.widget.index);
3275 case NWID_LAYER: return std::make_unique<NWidgetLayer>(nwid.u.widget.index);
3276
3277 case NWID_HSCROLLBAR: [[fallthrough]];
3278 case NWID_VSCROLLBAR: return std::make_unique<NWidgetScrollbar>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index);
3279
3280 case WPT_FUNCTION: return nwid.u.func_ptr();
3281
3282 default:
3283 assert((nwid.type & WWT_MASK) < WWT_LAST || (nwid.type & WWT_MASK) == NWID_BUTTON_DROPDOWN);
3284 return std::make_unique<NWidgetLeaf>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index, WidgetData{}, STR_NULL);
3285 }
3286}
3287
3301static 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)
3302{
3303 dest = nullptr;
3304
3305 if (IsAttributeWidgetPartType(nwid_begin->type)) [[unlikely]] throw std::runtime_error("Expected non-attribute NWidgetPart type");
3306 if (nwid_begin->type == WPT_ENDCONTAINER) return nwid_begin;
3307
3308 fill_dest = IsContainerWidgetType(nwid_begin->type);
3309 dest = MakeNWidget(*nwid_begin);
3310 if (dest == nullptr) return nwid_begin;
3311
3312 ++nwid_begin;
3313
3314 /* Once a widget is created, we're now looking for attributes. */
3315 while (nwid_begin != nwid_end && IsAttributeWidgetPartType(nwid_begin->type)) {
3316 ApplyNWidgetPartAttribute(*nwid_begin, dest.get());
3317 ++nwid_begin;
3318 }
3319
3320 return nwid_begin;
3321}
3322
3329{
3330 return tp == NWID_HORIZONTAL || tp == NWID_HORIZONTAL_LTR || tp == NWID_VERTICAL || tp == NWID_MATRIX
3331 || tp == WWT_PANEL || tp == WWT_FRAME || tp == WWT_INSET || tp == NWID_SELECTION || tp == NWID_LAYER;
3332}
3333
3341static 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)
3342{
3343 /* If *parent == nullptr, only the first widget is read and returned. Otherwise, *parent must point to either
3344 * a #NWidgetContainer or a #NWidgetBackground object, and parts are added as much as possible. */
3345 NWidgetContainer *nwid_cont = dynamic_cast<NWidgetContainer *>(parent.get());
3346 NWidgetBackground *nwid_parent = dynamic_cast<NWidgetBackground *>(parent.get());
3347 assert(parent == nullptr || (nwid_cont != nullptr && nwid_parent == nullptr) || (nwid_cont == nullptr && nwid_parent != nullptr));
3348
3349 while (nwid_begin != nwid_end) {
3350 std::unique_ptr<NWidgetBase> sub_widget = nullptr;
3351 bool fill_sub = false;
3352 nwid_begin = MakeNWidget(nwid_begin, nwid_end, sub_widget, fill_sub);
3353
3354 /* Break out of loop when end reached */
3355 if (sub_widget == nullptr) break;
3356
3357 /* If sub-widget is a container, recursively fill that container. */
3358 if (fill_sub && IsContainerWidgetType(sub_widget->type)) {
3359 nwid_begin = MakeWidgetTree(nwid_begin, nwid_end, sub_widget);
3360 }
3361
3362 /* Add sub_widget to parent container if available, otherwise return the widget to the caller. */
3363 if (nwid_cont != nullptr) nwid_cont->Add(std::move(sub_widget));
3364 if (nwid_parent != nullptr) nwid_parent->Add(std::move(sub_widget));
3365 if (nwid_cont == nullptr && nwid_parent == nullptr) {
3366 parent = std::move(sub_widget);
3367 return nwid_begin;
3368 }
3369 }
3370
3371 if (nwid_begin == nwid_end) return nwid_begin; // Reached the end of the array of parts?
3372
3373 assert(nwid_begin < nwid_end);
3374 assert(nwid_begin->type == WPT_ENDCONTAINER);
3375 return std::next(nwid_begin); // *nwid_begin is also 'used'
3376}
3377
3385std::unique_ptr<NWidgetBase> MakeNWidgets(std::span<const NWidgetPart> nwid_parts, std::unique_ptr<NWidgetBase> &&container)
3386{
3387 if (container == nullptr) container = std::make_unique<NWidgetVertical>();
3388 [[maybe_unused]] auto nwid_part = MakeWidgetTree(std::begin(nwid_parts), std::end(nwid_parts), container);
3389#ifdef WITH_ASSERT
3390 if (nwid_part != std::end(nwid_parts)) [[unlikely]] throw std::runtime_error("Did not consume all NWidgetParts");
3391#endif
3392 return std::move(container);
3393}
3394
3404std::unique_ptr<NWidgetBase> MakeWindowNWidgetTree(std::span<const NWidgetPart> nwid_parts, NWidgetStacked **shade_select)
3405{
3406 auto nwid_begin = std::begin(nwid_parts);
3407 auto nwid_end = std::end(nwid_parts);
3408
3409 *shade_select = nullptr;
3410
3411 /* Read the first widget recursively from the array. */
3412 std::unique_ptr<NWidgetBase> nwid = nullptr;
3413 nwid_begin = MakeWidgetTree(nwid_begin, nwid_end, nwid);
3414 assert(nwid != nullptr);
3415
3416 NWidgetHorizontal *hor_cont = dynamic_cast<NWidgetHorizontal *>(nwid.get());
3417
3418 auto root = std::make_unique<NWidgetVertical>();
3419 root->Add(std::move(nwid));
3420 if (nwid_begin == nwid_end) return root; // There is no body at all.
3421
3422 if (hor_cont != nullptr && hor_cont->GetWidgetOfType(WWT_CAPTION) != nullptr && hor_cont->GetWidgetOfType(WWT_SHADEBOX) != nullptr) {
3423 /* If the first widget has a title bar and a shade box, silently add a shade selection widget in the tree. */
3424 auto shade_stack = std::make_unique<NWidgetStacked>(-1);
3425 *shade_select = shade_stack.get();
3426 /* Load the remaining parts into the shade stack. */
3427 shade_stack->Add(MakeNWidgets({nwid_begin, nwid_end}, std::make_unique<NWidgetVertical>()));
3428 root->Add(std::move(shade_stack));
3429 return root;
3430 }
3431
3432 /* Load the remaining parts into 'root'. */
3433 return MakeNWidgets({nwid_begin, nwid_end}, std::move(root));
3434}
3435
3446std::unique_ptr<NWidgetBase> MakeCompanyButtonRows(WidgetID widget_first, WidgetID widget_last, Colours button_colour, int max_length, StringID button_tooltip, bool resizable)
3447{
3448 assert(max_length >= 1);
3449 std::unique_ptr<NWidgetVertical> vert = nullptr; // Storage for all rows.
3450 std::unique_ptr<NWidgetHorizontal> hor = nullptr; // Storage for buttons in one row.
3451 int hor_length = 0;
3452
3453 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON, nullptr, ZoomLevel::Normal);
3454 sprite_size.width += WidgetDimensions::unscaled.matrix.Horizontal();
3455 sprite_size.height += WidgetDimensions::unscaled.matrix.Vertical();
3456
3457 for (WidgetID widnum = widget_first; widnum <= widget_last; widnum++) {
3458 /* Ensure there is room in 'hor' for another button. */
3459 if (hor_length == max_length) {
3460 if (vert == nullptr) vert = std::make_unique<NWidgetVertical>();
3461 vert->Add(std::move(hor));
3462 hor = nullptr;
3463 hor_length = 0;
3464 }
3465 if (hor == nullptr) {
3466 hor = std::make_unique<NWidgetHorizontal>();
3467 hor_length = 0;
3468 }
3469
3470 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, button_colour, widnum);
3471 panel->SetMinimalSize(sprite_size.width, sprite_size.height);
3472 panel->SetFill(1, 1);
3473 if (resizable) panel->SetResize(1, 0);
3474 panel->SetToolTip(button_tooltip);
3475 hor->Add(std::move(panel));
3476 hor_length++;
3477 }
3478 if (vert == nullptr) return hor; // All buttons fit in a single row.
3479
3480 if (hor_length > 0 && hor_length < max_length) {
3481 /* Last row is partial, add a spacer at the end to force all buttons to the left. */
3482 auto spc = std::make_unique<NWidgetSpacer>(sprite_size.width, sprite_size.height);
3483 spc->SetFill(1, 1);
3484 if (resizable) spc->SetResize(1, 0);
3485 hor->Add(std::move(spc));
3486 }
3487 if (hor != nullptr) vert->Add(std::move(hor));
3488 return vert;
3489}
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:2348
NWidgetBase * GetWidgetOfType(WidgetType tp) override
Retrieve a widget by its type.
Definition widget.cpp:2358
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:2294
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2225
NWidgetBackground(WidgetType tp, Colours colour, WidgetID index, std::unique_ptr< NWidgetPIPContainer > &&child=nullptr)
Constructor parent nested widgets.
Definition widget.cpp:2157
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:2191
std::unique_ptr< NWidgetPIPContainer > child
Child widget.
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2312
void Add(std::unique_ptr< NWidgetBase > &&nwid)
Add a child to the parent.
Definition widget.cpp:2172
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:2306
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:2210
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 SetDirty(const Window *w) const
Mark the widget as 'dirty' (in need of repaint).
Definition widget.cpp:942
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.
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.
const WidgetID index
Index of the nested widget (-1 means 'not used').
virtual NWidgetBase * GetWidgetOfType(WidgetType tp)
Retrieve a widget by its type.
Definition widget.cpp:962
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).
virtual void FillWidgetLookup(WidgetLookup &widget_lookup)
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:926
Baseclass for container widgets.
void Add(std::unique_ptr< NWidgetBase > &&wid)
Append widget wid to container.
Definition widget.cpp:1303
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1310
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1318
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:1327
NWidgetBase * GetWidgetOfType(WidgetType tp) override
Retrieve a widget by its type.
Definition widget.cpp:1281
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:1235
bool IsDisabled() const
Return whether the widget is disabled.
void SetSprite(SpriteID sprite)
Set sprite of the nested widget.
Definition widget.cpp:1185
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:1225
WidgetID GetScrollbarIndex() const
Get the WidgetID of this nested widget's scrollbar.
Definition widget.cpp:1271
void SetAlignment(StringAlignment align)
Set the text/image alignment of the nested widget.
Definition widget.cpp:1253
void SetResizeWidgetType(ResizeWidgetValues type)
Set the resize widget type of the nested widget.
Definition widget.cpp:1215
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:1153
void SetSpriteTip(SpriteID sprite, StringID tool_tip)
Set sprite and tool tip of the nested widget.
Definition widget.cpp:1195
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:1262
WidgetID scrollbar_index
Index of an attached scrollbar.
StringID GetToolTip() const
Get the tool tip of the nested widget.
Definition widget.cpp:1244
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1276
void SetString(StringID string)
Set string of the nested widget.
Definition widget.cpp:1165
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:1206
void SetLowered(bool lowered)
Lower or raise the widget.
void SetStringTip(StringID string, StringID tool_tip)
Set string and tool tip of the nested widget.
Definition widget.cpp:1175
StringID tool_tip
Tooltip of the widget.
bool IsLowered() const
Return whether the widget is lowered.
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:1724
Horizontal container.
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1546
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:1615
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:1474
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1452
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1492
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2976
static void InvalidateDimensionCache()
Reset the cached dimensions.
Definition widget.cpp:2653
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:2680
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:2789
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:3120
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2071
int count
Amount of valid elements.
void SetClicked(int clicked)
Sets the clicked element in the matrix.
Definition widget.cpp:1940
int GetCurrentElement() const
Get current element.
Definition widget.cpp:1990
Scrollbar * sb
The scrollbar we're associated with.
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1995
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:1981
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:2126
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:2017
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:2041
int clicked
The currently clicked element.
void SetCount(int count)
Set the number of elements in this matrix.
Definition widget.cpp:1957
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:1539
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:1519
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:1034
bool UpdateSize(uint min_x, uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1117
uint min_x
Minimal horizontal size of only this widget.
FontSize uz_text_size
'Unscaled' font size, stored for resize calculation.
NWidgetResizeBase(WidgetType tp, WidgetID index, uint fill_x, uint fill_y)
Constructor for resizable nested widgets.
Definition widget.cpp:992
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:1073
void SetMinimalTextLines(uint8_t min_lines, uint8_t spacing, FontSize size)
Set minimal text lines for the widget.
Definition widget.cpp:1060
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:1097
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:1138
bool UpdateVerticalSize(uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1131
void SetResize(uint resize_x, uint resize_y)
Set resize step of the widget.
Definition widget.cpp:1084
void SetAspect(float ratio, AspectFlags flags=AspectFlag::ResizeX)
Set desired aspect ratio of this widget.
Definition widget.cpp:1003
void SetMinimalSizeAbsolute(uint min_x, uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1047
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:2597
NWidgetScrollbar(WidgetType tp, Colours colour, WidgetID index)
Scrollbar widget.
Definition widget.cpp:2555
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2576
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
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1931
void SetDirty(const Window *w) const override
Mark the widget as 'dirty' (in need of repaint).
Definition widget.cpp:1926
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1914
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:1338
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:1379
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1409
bool SetDisplayedPlane(int plane)
Select which plane to show (for NWID_SELECTION only).
Definition widget.cpp:1433
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1399
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1418
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:2413
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2377
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2370
void InitializeViewport(Window *w, std::variant< TileIndex, VehicleID > focus, ZoomLevel zoom)
Initialize the viewport of the window.
Definition widget.cpp:2404
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:2436
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:2510
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:2457
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:87
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:88
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:30
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:86
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:93
static constexpr uint WD_RESIZEBOX_WIDTH
Minimum width of a resize box widget.
Definition window_gui.h:85
static constexpr uint WD_STICKYBOX_WIDTH
Minimum width of a standard sticky box widget.
Definition window_gui.h:82
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:83
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:84
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:81
int hsep_indent
Width of indentation for tree layouts.
Definition window_gui.h:63
TypedIndexContainer< std::array< Colours, MAX_COMPANIES >, CompanyID > _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 CentreBounds(int min, int max, int size)
Determine where to position a centred object.
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:958
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:887
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:1024
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:1554
Dimension GetScaledSpriteSize(SpriteID sprid)
Scale sprite size for GUI.
Definition widget.cpp:68
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:250
@ FS_BEGIN
First font.
Definition gfx_type.h:257
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:251
StringAlignment
How to align the to-be drawn text.
Definition gfx_type.h:382
@ SA_TOP
Top align the text.
Definition gfx_type.h:388
@ SA_LEFT
Left align the text.
Definition gfx_type.h:383
@ SA_HOR_MASK
Mask for horizontal alignment.
Definition gfx_type.h:386
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:385
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:384
@ SA_VERT_MASK
Mask for vertical alignment.
Definition gfx_type.h:391
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition gfx_type.h:395
@ SA_BOTTOM
Bottom align the text.
Definition gfx_type.h:390
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:393
@ SA_VERT_CENTER
Vertically center the text.
Definition gfx_type.h:389
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:302
@ FILLRECT_CHECKER
Draw only every second pixel, used for greying-out.
Definition gfx_type.h:341
@ FILLRECT_RECOLOUR
Apply a recolour sprite to the screen content.
Definition gfx_type.h:342
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:3404
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:3385
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:955
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:1502
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:388
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:60
void DrawBoolButton(int x, int y, Colours button_colour, Colours background, bool state, bool clickable)
Draw a toggle button.
Functions for setting GUIs.
#define SETTING_BUTTON_WIDTH
Width of setting buttons.
#define SETTING_BUTTON_HEIGHT
Height of setting buttons.
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:1538
static const PaletteID PALETTE_TO_TRANSPARENT
This sets the sprite to transparent.
Definition sprites.h:1608
static const PaletteID PALETTE_NEWSPAPER
Recolour sprite for newspaper-greying.
Definition sprites.h:1610
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:425
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.
Rect Indent(int indent, bool end) const
Copy Rect and indent it from its position.
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:273
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:620
static int SortButtonWidth()
Get width of up/down arrow of sort button state.
Definition widget.cpp:826
void DrawWidgets() const
Paint all widgets of a window.
Definition widget.cpp:777
std::unique_ptr< ViewportData > viewport
Pointer to viewport data, if present.
Definition window_gui.h:318
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:503
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:326
virtual void OnScrollbarScroll(WidgetID widget)
Notify window that a scrollbar position has been updated.
Definition window_gui.h:719
void DrawSortButtonState(WidgetID widget, SortButtonState state) const
Draw a sort button's up or down arrow symbol.
Definition widget.cpp:809
virtual bool IsNewGRFInspectable() const
Is the data related to this window NewGRF inspectable?
Definition window_gui.h:859
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:606
Owner owner
The owner of the content shown in this window. Company colour is acquired from this variable.
Definition window_gui.h:316
int left
x position of left edge of the window
Definition window_gui.h:309
bool IsShaded() const
Is window shaded currently?
Definition window_gui.h:559
int top
y position of top edge of the window
Definition window_gui.h:310
const QueryString * GetQueryString(WidgetID widnum) const
Return the querystring associated to a editbox.
Definition window.cpp:332
WidgetLookup widget_lookup
Indexed access to the nested widget tree. Do not access directly, use Window::GetWidget() instead.
Definition window_gui.h:322
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:211
const NWID * GetWidget(WidgetID widnum) const
Get the nested widget with number widnum from the nested widget tree.
Definition window_gui.h:982
WindowFlags flags
Window flags.
Definition window_gui.h:300
std::unique_ptr< NWidgetBase > nested_root
Root of the nested tree.
Definition window_gui.h:321
int height
Height of the window (number of pixels down in y direction)
Definition window_gui.h:312
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:311
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
NWidgetPartContainer container
Part with container flags.
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.
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:704
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:298
Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
Apply 'scroll' to a rect to be drawn in.
Definition widget.cpp:2527
void ApplyNWidgetPartAttribute(const NWidgetPart &nwid, NWidgetBase *dest)
Apply an attribute NWidgetPart to an NWidget.
Definition widget.cpp:3149
static void DrawDefSizeBox(const Rect &r, Colours colour, bool clicked)
Draw a defsize box.
Definition widget.cpp:665
bool IsContainerWidgetType(WidgetType tp)
Test if WidgetType is a container widget.
Definition widget.cpp:3328
static void DrawDebugBox(const Rect &r, Colours colour, bool clicked)
Draw a NewGRF debug box.
Definition widget.cpp:676
static void DrawStickyBox(const Rect &r, Colours colour, bool clicked)
Draw a sticky box.
Definition widget.cpp:654
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:590
static void DrawImageTextButtons(const Rect &r, Colours colour, bool clicked, SpriteID img, TextColour text_colour, const std::string &text, StringAlignment align, FontSize fs)
Draw a button with image and rext.
Definition widget.cpp:373
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:120
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:452
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:550
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:755
Dimension GetScaledSpriteSize(SpriteID sprid)
Scale sprite size for GUI.
Definition widget.cpp:68
static std::unique_ptr< NWidgetBase > MakeNWidget(const NWidgetPart &nwid)
Make NWidget from an NWidgetPart.
Definition widget.cpp:3257
void SetupWidgetDimensions()
Set up pre-scaled versions of Widget Dimensions.
Definition widget.cpp:80
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:725
static void DrawShadeBox(const Rect &r, Colours colour, bool clicked)
Draw a shade box.
Definition widget.cpp:643
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:283
static void DrawImageButtons(const Rect &r, WidgetType type, Colours colour, bool clicked, SpriteID img, StringAlignment align)
Draw an image button.
Definition widget.cpp:353
static bool IsAttributeWidgetPartType(WidgetType tp)
Test if (an NWidgetPart) WidgetType is an attribute widget part type.
Definition widget.cpp:3138
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:401
void ScrollbarClickHandler(Window *w, NWidgetCore *nw, int x, int y)
Special handling for the scrollbar widget type.
Definition widget.cpp:259
static void DrawResizeBox(const Rect &r, Colours colour, bool at_left, bool clicked, bool bevel)
Draw a resize box.
Definition widget.cpp:689
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:149
static void DrawText(const Rect &r, TextColour colour, std::string_view str, StringAlignment align, FontSize fs)
Draw text.
Definition widget.cpp:418
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:194
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:436
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:3341
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:510
static RectPadding ScaleGUITrad(const RectPadding &r)
Scale a RectPadding to GUI zoom level.
Definition widget.cpp:49
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:3446
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:85
@ WWT_PUSHIMGTEXTBTN
Normal push-button (no toggle button) with image and text caption.
@ WPT_ALIGNMENT
Widget part for specifying text/image alignment.
Definition widget_type.h:91
@ 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:83
@ WWT_PUSHARROWBTN
Normal push-button (no toggle button) with arrow caption.
@ WWT_LABEL
Centered label.
Definition widget_type.h:49
@ NWID_BUTTON_DROPDOWN
Button with a drop-down.
Definition widget_type.h:75
@ NWID_SPACER
Invisible widget that takes some space.
Definition widget_type.h:71
@ WWT_EDITBOX
a textbox for typing
Definition widget_type.h:63
@ WWT_ARROWBTN
(Toggle) Button with an arrow
Definition widget_type.h:44
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:67
@ WWT_TEXTBTN
(Toggle) Button with text
Definition widget_type.h:45
@ WPT_SCROLLBAR
Widget part for attaching a scrollbar.
Definition widget_type.h:92
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:40
@ WPT_ASPECT
Widget part for specifying aspect ratio.
Definition widget_type.h:93
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition widget_type.h:58
@ WPT_RESIZE
Widget part for specifying resizing.
Definition widget_type.h:82
@ WWT_IMGTEXTBTN
(Toggle) Button with image and text
Definition widget_type.h:48
@ WWT_MATRIX
Grid of rows and columns.
Definition widget_type.h:51
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition widget_type.h:56
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition widget_type.h:53
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:77
@ WPT_TEXTSTYLE
Widget part for specifying text colour.
Definition widget_type.h:90
@ WPT_PADDING
Widget part for specifying a padding.
Definition widget_type.h:87
@ WWT_BOOLBTN
Standard boolean toggle button.
Definition widget_type.h:47
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:69
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition widget_type.h:61
@ WWT_TEXTBTN_2
(Toggle) Button with diff text when clicked
Definition widget_type.h:46
@ WWT_FRAME
Frame.
Definition widget_type.h:52
@ WPT_MINTEXTLINES
Widget part for specifying minimal number of lines of text.
Definition widget_type.h:84
@ 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:64
@ WPT_ATTRIBUTE_END
End marker for attribute NWidgetPart types.
Definition widget_type.h:94
@ NWID_HSCROLLBAR
Horizontal scrollbar.
Definition widget_type.h:76
@ WPT_ENDCONTAINER
Widget part to denote end of a container.
Definition widget_type.h:97
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition widget_type.h:60
@ WPT_PIPRATIO
Widget part for specifying pre/inter/post ratio for containers.
Definition widget_type.h:89
@ WPT_DATATIP
Widget part for specifying data and tooltip.
Definition widget_type.h:86
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX)
Definition widget_type.h:57
@ WPT_ATTRIBUTE_BEGIN
Begin marker for attribute NWidgetPart types.
Definition widget_type.h:81
@ WPT_PIPSPACE
Widget part for specifying pre/inter/post space for containers.
Definition widget_type.h:88
@ NWID_MATRIX
Matrix container.
Definition widget_type.h:70
@ NWID_VIEWPORT
Nested widget containing a viewport.
Definition widget_type.h:74
@ WWT_DROPDOWN
Drop down list.
Definition widget_type.h:62
@ WWT_TEXT
Pure simple text.
Definition widget_type.h:50
@ NWID_HORIZONTAL_LTR
Horizontal container that doesn't change the order of the widgets for RTL languages.
Definition widget_type.h:68
@ WPT_FUNCTION
Widget part for calling a user function.
Definition widget_type.h:96
@ WWT_DEBUGBOX
NewGRF debug box (at top-right of a window, between WWT_CAPTION and WWT_SHADEBOX)
Definition widget_type.h:55
@ NWID_LAYER
Layered widgets, all visible together.
Definition widget_type.h:73
@ NWID_SELECTION
Stacked widgets, only one visible at a time (eg in a panel with tabs).
Definition widget_type.h:72
@ 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.
@ 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:298
@ 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:216
@ SBS_DOWN
Sort ascending.
Definition window_gui.h:218
@ SBS_OFF
Do not sort (with this button).
Definition window_gui.h:217
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 > ZoomLevel::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
@ Normal
The normal zoom level.