OpenTTD Source 20260206-master-g4d4e37dbf1
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
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{
86 if (_settings_client.gui.scale_bevels) {
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 std::pair<int, int> HandleScrollbarHittest(const Scrollbar *sb, int mi, int ma, bool horizontal)
150{
151 /* Base for reversion */
152 int rev_base = mi + ma;
153 int button_size = horizontal ? NWidgetScrollbar::GetHorizontalDimension().width : NWidgetScrollbar::GetVerticalDimension().height;
154
155 mi += button_size; // now points to just after the up/left-button
156 ma -= button_size; // now points to just before the down/right-button
157
158 int count = sb->GetCount();
159 int cap = sb->GetCapacity();
160
161 if (count > cap) {
162 int height = ma + 1 - mi;
163 int slider_height = std::max(button_size, cap * height / count);
164 height -= slider_height;
165
166 mi += height * sb->GetPosition() / (count - cap);
167 ma = mi + slider_height - 1;
168 }
169
170 /* Reverse coordinates for RTL. */
171 if (horizontal && _current_text_dir == TD_RTL) return {rev_base - ma, rev_base - mi};
172
173 return {mi, ma};
174}
175
185static void ScrollbarClickPositioning(Window *w, NWidgetScrollbar *sb, int x, int y, int mi, int ma)
186{
187 int pos;
188 int button_size;
189 bool rtl = false;
190 bool changed = false;
191
192 if (sb->type == NWID_HSCROLLBAR) {
193 pos = x;
194 rtl = _current_text_dir == TD_RTL;
195 button_size = NWidgetScrollbar::GetHorizontalDimension().width;
196 } else {
197 pos = y;
198 button_size = NWidgetScrollbar::GetVerticalDimension().height;
199 }
200 if (pos < mi + button_size) {
201 /* Pressing the upper button? */
203 if (_scroller_click_timeout <= 1) {
204 _scroller_click_timeout = 3;
205 changed = sb->UpdatePosition(rtl ? 1 : -1);
206 }
207 w->mouse_capture_widget = sb->GetIndex();
208 } else if (pos >= ma - button_size) {
209 /* Pressing the lower button? */
211
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 {
218 auto [start, end] = HandleScrollbarHittest(sb, mi, ma, sb->type == NWID_HSCROLLBAR);
219
220 if (pos < start) {
221 changed = sb->UpdatePosition(rtl ? 1 : -1, Scrollbar::SS_BIG);
222 } else if (pos > end) {
223 changed = sb->UpdatePosition(rtl ? -1 : 1, Scrollbar::SS_BIG);
224 } else {
225 _scrollbar_start_pos = start - mi - button_size;
226 _scrollbar_size = ma - mi - button_size * 2 - (end - start);
227 w->mouse_capture_widget = sb->GetIndex();
228 _cursorpos_drag_start = _cursor.pos;
229 }
230 }
231
232 if (changed) {
233 /* Position changed so refresh the window */
234 w->OnScrollbarScroll(sb->GetIndex());
235 w->SetDirty();
236 } else {
237 /* No change so only refresh this scrollbar */
238 sb->SetDirty(w);
239 }
240}
241
250void ScrollbarClickHandler(Window *w, NWidgetCore *nw, int x, int y)
251{
252 int mi, ma;
253
254 if (nw->type == NWID_HSCROLLBAR) {
255 mi = nw->pos_x;
256 ma = nw->pos_x + nw->current_x;
257 } else {
258 mi = nw->pos_y;
259 ma = nw->pos_y + nw->current_y;
260 }
261 NWidgetScrollbar *scrollbar = dynamic_cast<NWidgetScrollbar*>(nw);
262 assert(scrollbar != nullptr);
263 ScrollbarClickPositioning(w, scrollbar, x, y, mi, ma);
264}
265
274WidgetID GetWidgetFromPos(const Window *w, int x, int y)
275{
276 NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
277 return (nw != nullptr) ? nw->GetIndex() : INVALID_WIDGET;
278}
279
289void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
290{
291 if (flags.Test(FrameFlag::Transparent)) {
292 GfxFillRect(left, top, right, bottom, PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOUR);
293 } else {
294 assert(colour < COLOUR_END);
295
296 const PixelColour dark = GetColourGradient(colour, SHADE_DARK);
297 const PixelColour medium_dark = GetColourGradient(colour, SHADE_LIGHT);
298 const PixelColour medium_light = GetColourGradient(colour, SHADE_LIGHTER);
299 const PixelColour light = GetColourGradient(colour, SHADE_LIGHTEST);
300 PixelColour interior;
301
302 Rect outer = {left, top, right, bottom}; // Outside rectangle
303 Rect inner = outer.Shrink(WidgetDimensions::scaled.bevel); // Inside rectangle
304
305 if (flags.Test(FrameFlag::Lowered)) {
306 GfxFillRect(outer.left, outer.top, inner.left - 1, outer.bottom, dark); // Left
307 GfxFillRect(inner.left, outer.top, outer.right, inner.top - 1, dark); // Top
308 GfxFillRect(inner.right + 1, inner.top, outer.right, inner.bottom, light); // Right
309 GfxFillRect(inner.left, inner.bottom + 1, outer.right, outer.bottom, light); // Bottom
310 interior = (flags.Test(FrameFlag::Darkened) ? medium_dark : medium_light);
311 } else {
312 GfxFillRect(outer.left, outer.top, inner.left - 1, inner.bottom, light); // Left
313 GfxFillRect(inner.left, outer.top, inner.right, inner.top - 1, light); // Top
314 GfxFillRect(inner.right + 1, outer.top, outer.right, inner.bottom, dark); // Right
315 GfxFillRect(outer.left, inner.bottom + 1, outer.right, outer.bottom, dark); // Bottom
316 interior = medium_dark;
317 }
318 if (!flags.Test(FrameFlag::BorderOnly)) {
319 GfxFillRect(inner.left, inner.top, inner.right, inner.bottom, interior); // Inner
320 }
321 }
322}
323
324void DrawSpriteIgnorePadding(SpriteID img, PaletteID pal, const Rect &r, StringAlignment align)
325{
326 Point offset;
327 Dimension d = GetSpriteSize(img, &offset);
328 d.width -= offset.x;
329 d.height -= offset.y;
330
331 Point p = GetAlignedPosition(r, d, align);
332 DrawSprite(img, pal, p.x - offset.x, p.y - offset.y);
333}
334
344static inline void DrawImageButtons(const Rect &r, WidgetType type, Colours colour, bool clicked, SpriteID img, StringAlignment align)
345{
346 assert(img != 0);
347 DrawFrameRect(r, colour, clicked ? FrameFlag::Lowered : FrameFlags{});
348
349 if ((type & WWT_MASK) == WWT_IMGBTN_2 && clicked) img++; // Show different image when clicked for #WWT_IMGBTN_2.
350 DrawSpriteIgnorePadding(img, PAL_NONE, r, align);
351}
352
364static inline void DrawImageTextButtons(const Rect &r, Colours colour, bool clicked, SpriteID img, TextColour text_colour, const std::string &text, StringAlignment align, FontSize fs)
365{
366 DrawFrameRect(r, colour, clicked ? FrameFlag::Lowered : FrameFlags{});
367
368 bool rtl = _current_text_dir == TD_RTL;
369 int image_width = img != 0 ? GetScaledSpriteSize(img).width : 0;
370 Rect r_img = r.Shrink(WidgetDimensions::scaled.framerect).WithWidth(image_width, rtl);
371 Rect r_text = r.Shrink(WidgetDimensions::scaled.framerect).Indent(image_width + WidgetDimensions::scaled.hsep_wide, rtl);
372
373 if (img != 0) {
374 DrawSpriteIgnorePadding(img, PAL_NONE, r_img, SA_HOR_CENTER | (align & SA_VERT_MASK));
375 }
376
377 if (!text.empty()) {
378 Dimension d = GetStringBoundingBox(text, fs);
379 Point p = GetAlignedPosition(r_text, d, align);
380 DrawString(r_text.left, r_text.right, p.y, text, text_colour, align, false, fs);
381 }
382}
383
392static inline void DrawLabel(const Rect &r, TextColour colour, std::string_view str, StringAlignment align, FontSize fs)
393{
394 if (str.empty()) return;
395
396 Dimension d = GetStringBoundingBox(str, fs);
397 Point p = GetAlignedPosition(r, d, align);
398 DrawString(r.left, r.right, p.y, str, colour, align, false, fs);
399}
400
409static inline void DrawText(const Rect &r, TextColour colour, std::string_view str, StringAlignment align, FontSize fs)
410{
411 if (str.empty()) return;
412
413 Dimension d = GetStringBoundingBox(str, fs);
414 Point p = GetAlignedPosition(r, d, align);
415 DrawString(r.left, r.right, p.y, str, colour, align, false, fs);
416}
417
427static inline void DrawInset(const Rect &r, Colours colour, TextColour text_colour, std::string_view str, StringAlignment align, FontSize fs)
428{
430 if (!str.empty()) DrawString(r.Shrink(WidgetDimensions::scaled.inset), str, text_colour, align, false, fs);
431}
432
443static inline void DrawMatrix(const Rect &r, Colours colour, bool clicked, uint32_t num_columns, uint32_t num_rows, uint resize_x, uint resize_y)
444{
445 DrawFrameRect(r, colour, clicked ? FrameFlag::Lowered : FrameFlags{});
446
447 int column_width; // Width of a single column in the matrix.
448 if (num_columns == 0) {
449 column_width = resize_x;
450 num_columns = r.Width() / column_width;
451 } else {
452 column_width = r.Width() / num_columns;
453 }
454
455 int row_height; // Height of a single row in the matrix.
456 if (num_rows == 0) {
457 row_height = resize_y;
458 num_rows = r.Height() / row_height;
459 } else {
460 row_height = r.Height() / num_rows;
461 }
462
463 PixelColour col = GetColourGradient(colour, SHADE_LIGHTER);
464
465 int x = r.left;
466 for (int ctr = num_columns; ctr > 1; ctr--) {
467 x += column_width;
468 GfxFillRect(x, r.top + WidgetDimensions::scaled.bevel.top, x + WidgetDimensions::scaled.bevel.left - 1, r.bottom - WidgetDimensions::scaled.bevel.bottom, col);
469 }
470
471 x = r.top;
472 for (int ctr = num_rows; ctr > 1; ctr--) {
473 x += row_height;
474 GfxFillRect(r.left + WidgetDimensions::scaled.bevel.left, x, r.right - WidgetDimensions::scaled.bevel.right, x + WidgetDimensions::scaled.bevel.top - 1, col);
475 }
476
477 col = GetColourGradient(colour, SHADE_NORMAL);
478
479 x = r.left - 1;
480 for (int ctr = num_columns; ctr > 1; ctr--) {
481 x += column_width;
482 GfxFillRect(x - WidgetDimensions::scaled.bevel.right + 1, r.top + WidgetDimensions::scaled.bevel.top, x, r.bottom - WidgetDimensions::scaled.bevel.bottom, col);
483 }
484
485 x = r.top - 1;
486 for (int ctr = num_rows; ctr > 1; ctr--) {
487 x += row_height;
488 GfxFillRect(r.left + WidgetDimensions::scaled.bevel.left, x - WidgetDimensions::scaled.bevel.bottom + 1, r.right - WidgetDimensions::scaled.bevel.right, x, col);
489 }
490}
491
501static inline void DrawVerticalScrollbar(const Rect &r, Colours colour, bool up_clicked, bool bar_dragged, bool down_clicked, const Scrollbar *scrollbar)
502{
503 int height = NWidgetScrollbar::GetVerticalDimension().height;
504
505 /* draw up/down buttons */
506 DrawImageButtons(r.WithHeight(height, false), NWID_VSCROLLBAR, colour, up_clicked, SPR_ARROW_UP, SA_CENTER);
507 DrawImageButtons(r.WithHeight(height, true), NWID_VSCROLLBAR, colour, down_clicked, SPR_ARROW_DOWN, SA_CENTER);
508
509 PixelColour c1 = GetColourGradient(colour, SHADE_DARK);
510 PixelColour c2 = GetColourGradient(colour, SHADE_LIGHTEST);
511
512 /* draw "shaded" background */
513 Rect bg = r.Shrink(0, height);
514 GfxFillRect(bg, c2);
516
517 /* track positions. These fractions are based on original 1x dimensions, but scale better. */
518 int left = r.left + r.Width() * 3 / 11; /* left track is positioned 3/11ths from the left */
519 int right = r.left + r.Width() * 8 / 11; /* right track is positioned 8/11ths from the left */
520 const uint8_t bl = WidgetDimensions::scaled.bevel.left;
521 const uint8_t br = WidgetDimensions::scaled.bevel.right;
522
523 /* draw shaded lines */
524 GfxFillRect(bg.WithX(left - bl, left - 1), c1);
525 GfxFillRect(bg.WithX(left, left + br - 1), c2);
526 GfxFillRect(bg.WithX(right - bl, right - 1), c1);
527 GfxFillRect(bg.WithX(right, right + br - 1), c2);
528
529 auto [top, bottom] = HandleScrollbarHittest(scrollbar, r.top, r.bottom, false);
530 DrawFrameRect(r.left, top, r.right, bottom, colour, bar_dragged ? FrameFlag::Lowered : FrameFlags{});
531}
532
542static inline void DrawHorizontalScrollbar(const Rect &r, Colours colour, bool left_clicked, bool bar_dragged, bool right_clicked, const Scrollbar *scrollbar)
543{
544 int width = NWidgetScrollbar::GetHorizontalDimension().width;
545
546 DrawImageButtons(r.WithWidth(width, false), NWID_HSCROLLBAR, colour, left_clicked, SPR_ARROW_LEFT, SA_CENTER);
547 DrawImageButtons(r.WithWidth(width, true), NWID_HSCROLLBAR, colour, right_clicked, SPR_ARROW_RIGHT, SA_CENTER);
548
549 PixelColour c1 = GetColourGradient(colour, SHADE_DARK);
550 PixelColour c2 = GetColourGradient(colour, SHADE_LIGHTEST);
551
552 /* draw "shaded" background */
553 Rect bg = r.Shrink(width, 0);
554 GfxFillRect(bg, c2);
556
557 /* track positions. These fractions are based on original 1x dimensions, but scale better. */
558 int top = r.top + r.Height() * 3 / 11; /* top track is positioned 3/11ths from the top */
559 int bottom = r.top + r.Height() * 8 / 11; /* bottom track is positioned 8/11ths from the top */
560 const uint8_t bt = WidgetDimensions::scaled.bevel.top;
561 const uint8_t bb = WidgetDimensions::scaled.bevel.bottom;
562
563 /* draw shaded lines */
564 GfxFillRect(bg.WithY(top - bt, top - 1), c1);
565 GfxFillRect(bg.WithY(top, top + bb - 1), c2);
566 GfxFillRect(bg.WithY(bottom - bt, bottom - 1), c1);
567 GfxFillRect(bg.WithY(bottom, bottom + bb - 1), c2);
568
569 /* draw actual scrollbar */
570 auto [left, right] = HandleScrollbarHittest(scrollbar, r.left, r.right, true);
571 DrawFrameRect(left, r.top, right, r.bottom, colour, bar_dragged ? FrameFlag::Lowered : FrameFlags{});
572}
573
583static inline void DrawFrame(const Rect &r, Colours colour, TextColour text_colour, std::string_view str, StringAlignment align, FontSize fs)
584{
585 int x2 = r.left; // by default the left side is the left side of the widget
586
587 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);
588
589 PixelColour c1 = GetColourGradient(colour, SHADE_DARK);
590 PixelColour c2 = GetColourGradient(colour, SHADE_LIGHTEST);
591
592 /* If the frame has text, adjust the top bar to fit half-way through */
593 Rect inner = r.Shrink(ScaleGUITrad(1));
594 if (!str.empty()) inner.top = r.top + GetCharacterHeight(FS_NORMAL) / 2;
595
596 Rect outer = inner.Expand(WidgetDimensions::scaled.bevel);
597 Rect inside = inner.Shrink(WidgetDimensions::scaled.bevel);
598
599 if (_current_text_dir == TD_LTR) {
600 /* Line from upper left corner to start of text */
601 GfxFillRect(outer.left, outer.top, r.left + WidgetDimensions::scaled.frametext.left - WidgetDimensions::scaled.bevel.left - 1, inner.top - 1, c1);
602 GfxFillRect(inner.left, inner.top, r.left + WidgetDimensions::scaled.frametext.left - WidgetDimensions::scaled.bevel.left - 1, inside.top - 1, c2);
603
604 /* Line from end of text to upper right corner */
605 GfxFillRect(x2 + WidgetDimensions::scaled.bevel.right, outer.top, inner.right, inner.top - 1, c1);
606 GfxFillRect(x2 + WidgetDimensions::scaled.bevel.right, inner.top, inside.right, inside.top - 1, c2);
607 } else {
608 /* Line from upper left corner to start of text */
609 GfxFillRect(outer.left, outer.top, x2 - WidgetDimensions::scaled.bevel.left - 1, inner.top - 1, c1);
610 GfxFillRect(inner.left, inner.top, x2 - WidgetDimensions::scaled.bevel.left - 1, inside.top - 1, c2);
611
612 /* Line from end of text to upper right corner */
613 GfxFillRect(r.right - WidgetDimensions::scaled.frametext.right + WidgetDimensions::scaled.bevel.right, outer.top, inner.right, inner.top - 1, c1);
614 GfxFillRect(r.right - WidgetDimensions::scaled.frametext.right + WidgetDimensions::scaled.bevel.right, inner.top, inside.right, inside.top - 1, c2);
615 }
616
617 /* Line from upper left corner to bottom left corner */
618 GfxFillRect(outer.left, inner.top, inner.left - 1, inner.bottom, c1);
619 GfxFillRect(inner.left, inside.top, inside.left - 1, inside.bottom, c2);
620
621 /* Line from upper right corner to bottom right corner */
622 GfxFillRect(inside.right + 1, inner.top, inner.right, inside.bottom, c1);
623 GfxFillRect(inner.right + 1, outer.top, outer.right, inner.bottom, c2);
624
625 /* Line from bottom left corner to bottom right corner */
626 GfxFillRect(inner.left, inside.bottom + 1, inner.right, inner.bottom, c1);
627 GfxFillRect(outer.left, inner.bottom + 1, outer.right, outer.bottom, c2);
628}
629
636static inline void DrawShadeBox(const Rect &r, Colours colour, bool clicked)
637{
638 DrawImageButtons(r, WWT_SHADEBOX, colour, clicked, clicked ? SPR_WINDOW_SHADE: SPR_WINDOW_UNSHADE, SA_CENTER);
639}
640
647static inline void DrawStickyBox(const Rect &r, Colours colour, bool clicked)
648{
649 DrawImageButtons(r, WWT_STICKYBOX, colour, clicked, clicked ? SPR_PIN_UP : SPR_PIN_DOWN, SA_CENTER);
650}
651
658static inline void DrawDefSizeBox(const Rect &r, Colours colour, bool clicked)
659{
660 DrawImageButtons(r, WWT_DEFSIZEBOX, colour, clicked, SPR_WINDOW_DEFSIZE, SA_CENTER);
661}
662
669static inline void DrawDebugBox(const Rect &r, Colours colour, bool clicked)
670{
671 DrawImageButtons(r, WWT_DEBUGBOX, colour, clicked, SPR_WINDOW_DEBUG, SA_CENTER);
672}
673
682static inline void DrawResizeBox(const Rect &r, Colours colour, bool at_left, bool clicked, bool bevel)
683{
684 if (bevel) {
685 DrawFrameRect(r, colour, clicked ? FrameFlag::Lowered : FrameFlags{});
686 } else if (clicked) {
687 GfxFillRect(r.Shrink(WidgetDimensions::scaled.bevel), GetColourGradient(colour, SHADE_LIGHTER));
688 }
689 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));
690}
691
697static inline void DrawCloseBox(const Rect &r, Colours colour)
698{
699 if (colour != COLOUR_WHITE) DrawFrameRect(r, colour, {});
700 Point offset;
701 Dimension d = GetSpriteSize(SPR_CLOSEBOX, &offset);
702 d.width -= offset.x;
703 d.height -= offset.y;
704 int s = ScaleSpriteTrad(1); // Offset to account for shadow of SPR_CLOSEBOX.
705 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);
706}
707
718void DrawCaption(const Rect &r, Colours colour, Owner owner, TextColour text_colour, std::string_view str, StringAlignment align, FontSize fs)
719{
720 bool company_owned = owner < MAX_COMPANIES;
721
725
726 if (company_owned) {
728 }
729
730 if (str.empty()) return;
731
733 Point p = GetAlignedPosition(r, d, align);
734 DrawString(r.left + WidgetDimensions::scaled.captiontext.left, r.right - WidgetDimensions::scaled.captiontext.left, p.y, str, text_colour, align, false, fs);
735}
736
748static inline void DrawButtonDropdown(const Rect &r, Colours colour, bool clicked_button, bool clicked_dropdown, std::string_view str, StringAlignment align)
749{
750 bool rtl = _current_text_dir == TD_RTL;
751
752 Rect text = r.Indent(NWidgetLeaf::dropdown_dimension.width, !rtl);
753 DrawFrameRect(text, colour, clicked_button ? FrameFlag::Lowered : FrameFlags{});
754 if (!str.empty()) {
755 text = text.CentreToHeight(GetCharacterHeight(FS_NORMAL)).Shrink(WidgetDimensions::scaled.dropdowntext, RectPadding::zero);
756 DrawString(text, str, TC_BLACK, align);
757 }
758
759 Rect button = r.WithWidth(NWidgetLeaf::dropdown_dimension.width, !rtl);
760 DrawImageButtons(button, WWT_DROPDOWN, colour, clicked_dropdown, SPR_ARROW_DOWN, SA_CENTER);
761}
762
767{
768 this->nested_root->Draw(this);
769
770 if (this->flags.Test(WindowFlag::WhiteBorder)) {
771 DrawFrameRect(0, 0, this->width - 1, this->height - 1, COLOUR_WHITE, FrameFlag::BorderOnly);
772 }
773
774 if (this->flags.Test(WindowFlag::Highlighted)) {
775 extern bool _window_highlight_colour;
776 for (const auto &pair : this->widget_lookup) {
777 const NWidgetBase *widget = pair.second;
778 if (!widget->IsHighlighted()) continue;
779
780 Rect outer = widget->GetCurrentRect();
781 Rect inner = outer.Shrink(WidgetDimensions::scaled.bevel).Expand(1);
782
784
785 GfxFillRect(outer.left, outer.top, inner.left, inner.bottom, colour);
786 GfxFillRect(inner.left + 1, outer.top, inner.right - 1, inner.top, colour);
787 GfxFillRect(inner.right, outer.top, outer.right, inner.bottom, colour);
788 GfxFillRect(outer.left + 1, inner.bottom, outer.right - 1, outer.bottom, colour);
789 }
790 }
791}
792
799{
800 if (state == SBS_OFF) return;
801
802 assert(!this->widget_lookup.empty());
803 Rect r = this->GetWidget<NWidgetBase>(widget)->GetCurrentRect();
804
805 /* Sort button uses the same sprites as vertical scrollbar */
806 Dimension dim = NWidgetScrollbar::GetVerticalDimension();
807
808 DrawSpriteIgnorePadding(state == SBS_DOWN ? SPR_ARROW_DOWN : SPR_ARROW_UP, PAL_NONE, r.WithWidth(dim.width, _current_text_dir == TD_LTR), SA_CENTER);
809}
810
816{
817 return NWidgetScrollbar::GetVerticalDimension().width + 1;
818}
819
820bool _draw_widget_outlines;
821
822static void DrawOutline(const Window *, const NWidgetBase *wid)
823{
824 if (!_draw_widget_outlines || wid->current_x == 0 || wid->current_y == 0) return;
825
826 DrawRectOutline(wid->GetCurrentRect(), PC_WHITE, 1, 4);
827}
828
881
895
909
916{
917 if (this->index >= 0) widget_lookup[this->index] = this;
918}
919
926
931void NWidgetBase::SetDirty(const Window *w) const
932{
933 int abs_left = w->left + this->pos_x;
934 int abs_top = w->top + this->pos_y;
935 AddDirtyBlock(abs_left, abs_top, abs_left + this->current_x, abs_top + this->current_y);
936}
937
945
952{
953 return (this->type == tp) ? this : nullptr;
954}
955
956void NWidgetBase::ApplyAspectRatio()
957{
958 if (this->aspect_ratio == 0) return;
959 if (this->smallest_x == 0 || this->smallest_y == 0) return;
960
961 uint x = this->smallest_x;
962 uint y = this->smallest_y;
963 if (this->aspect_flags.Test(AspectFlag::ResizeX)) x = std::max(this->smallest_x, static_cast<uint>(this->smallest_y * std::abs(this->aspect_ratio)));
964 if (this->aspect_flags.Test(AspectFlag::ResizeY)) y = std::max(this->smallest_y, static_cast<uint>(this->smallest_x / std::abs(this->aspect_ratio)));
965
966 this->smallest_x = x;
967 this->smallest_y = y;
968}
969
974
983{
984 this->fill_x = fill_x;
985 this->fill_y = fill_y;
986}
987
993void NWidgetResizeBase::SetAspect(float ratio, AspectFlags flags)
994{
995 this->aspect_ratio = ratio;
996 this->aspect_flags = flags;
997}
998
1005void NWidgetResizeBase::SetAspect(int x_ratio, int y_ratio, AspectFlags flags)
1006{
1007 this->SetAspect(static_cast<float>(x_ratio) / static_cast<float>(y_ratio), flags);
1008}
1009
1011{
1012 if (!this->absolute) {
1013 this->min_x = ScaleGUITrad(this->uz_min_x);
1014 this->min_y = std::max(ScaleGUITrad(this->uz_min_y), this->uz_text_lines * GetCharacterHeight(this->uz_text_size) + ScaleGUITrad(this->uz_text_spacing));
1015 }
1017}
1018
1025{
1026 this->uz_min_x = std::max(this->uz_min_x, min_x);
1027 this->uz_min_y = std::max(this->uz_min_y, min_y);
1028 this->min_x = ScaleGUITrad(this->uz_min_x);
1029 this->min_y = std::max(ScaleGUITrad(this->uz_min_y), this->uz_text_lines * GetCharacterHeight(this->uz_text_size) + ScaleGUITrad(this->uz_text_spacing));
1030}
1031
1038{
1039 this->absolute = true;
1040 this->min_x = std::max(this->min_x, min_x);
1041 this->min_y = std::max(this->min_y, min_y);
1042}
1043
1050void NWidgetResizeBase::SetMinimalTextLines(uint8_t min_lines, uint8_t spacing, FontSize size)
1051{
1052 this->uz_text_lines = min_lines;
1053 this->uz_text_spacing = spacing;
1054 this->uz_text_size = size;
1055 this->min_y = std::max(ScaleGUITrad(this->uz_min_y), this->uz_text_lines * GetCharacterHeight(this->uz_text_size) + ScaleGUITrad(this->uz_text_spacing));
1056}
1057
1064{
1065 this->fill_x = fill_x;
1066 this->fill_y = fill_y;
1067}
1068
1075{
1076 this->resize_x = resize_x;
1077 this->resize_y = resize_y;
1078}
1079
1087bool NWidgetResizeBase::UpdateMultilineWidgetSize(const std::string &str, int max_lines)
1088{
1089 int y = GetStringHeight(str, this->current_x);
1090 if (y > max_lines * GetCharacterHeight(FS_NORMAL)) {
1091 /* Text at the current width is too tall, so try to guess a better width. */
1093 d.height *= max_lines;
1094 d.width /= 2;
1095 return this->UpdateSize(d.width, d.height);
1096 }
1097 return this->UpdateVerticalSize(y);
1098}
1099
1108{
1109 if (min_x == this->min_x && min_y == this->min_y) return false;
1110 this->min_x = min_x;
1111 this->min_y = min_y;
1112 return true;
1113}
1114
1122{
1123 if (min_y == this->min_y) return false;
1124 this->min_y = min_y;
1125 return true;
1126}
1127
1128void NWidgetResizeBase::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool)
1129{
1130 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1131}
1132
1144{
1145 this->colour = colour;
1146 this->widget_data = widget_data;
1147 this->SetToolTip(tool_tip);
1148 this->text_colour = tp == WWT_CAPTION ? TC_WHITE : TC_BLACK;
1149}
1150
1156{
1157 this->widget_data.string = string;
1158}
1159
1166{
1167 this->SetString(string);
1168 this->SetToolTip(tool_tip);
1169}
1170
1176{
1177 this->widget_data.sprite = sprite;
1178}
1179
1186{
1187 this->SetSprite(sprite);
1188 this->SetToolTip(tool_tip);
1189}
1190
1196void NWidgetCore::SetMatrixDimension(uint32_t columns, uint32_t rows)
1197{
1198 this->widget_data.matrix = { columns, rows };
1199}
1200
1206{
1207 this->widget_data.resize_widget_type = type;
1208}
1209
1216{
1217 this->text_colour = colour;
1218 this->text_size = size;
1219}
1220
1226{
1227 this->tool_tip = tool_tip;
1228}
1229
1235{
1236 return this->tool_tip;
1237}
1238
1244{
1245 this->align = align;
1246}
1247
1253{
1254 return this->widget_data.string;
1255}
1256
1262{
1263 return this->scrollbar_index;
1264}
1265
1267{
1268 return (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) ? this : nullptr;
1269}
1270
1272{
1273 if (this->type == tp) return this;
1274 for (const auto &child_wid : this->children) {
1275 NWidgetBase *nwid = child_wid->GetWidgetOfType(tp);
1276 if (nwid != nullptr) return nwid;
1277 }
1278 return nullptr;
1279}
1280
1282{
1283 for (const auto &child_wid : this->children) {
1284 child_wid->AdjustPaddingForZoom();
1285 }
1287}
1288
1293void NWidgetContainer::Add(std::unique_ptr<NWidgetBase> &&wid)
1294{
1295 assert(wid != nullptr);
1296 wid->parent = this;
1297 this->children.push_back(std::move(wid));
1298}
1299
1301{
1302 this->NWidgetBase::FillWidgetLookup(widget_lookup);
1303 for (const auto &child_wid : this->children) {
1304 child_wid->FillWidgetLookup(widget_lookup);
1305 }
1306}
1307
1309{
1310 for (const auto &child_wid : this->children) {
1311 child_wid->Draw(w);
1312 }
1313
1314 DrawOutline(w, this);
1315}
1316
1318{
1319 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
1320
1321 for (const auto &child_wid : this->children) {
1322 NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
1323 if (nwid != nullptr) return nwid;
1324 }
1325 return nullptr;
1326}
1327
1329{
1330 /* Zero size plane selected */
1331 if (this->shown_plane >= SZSP_BEGIN) {
1332 Dimension size = {0, 0};
1333 Dimension padding = {0, 0};
1334 Dimension fill = {(this->shown_plane == SZSP_HORIZONTAL), (this->shown_plane == SZSP_VERTICAL)};
1335 Dimension resize = {(this->shown_plane == SZSP_HORIZONTAL), (this->shown_plane == SZSP_VERTICAL)};
1336 /* Here we're primarily interested in the value of resize */
1337 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
1338
1339 this->smallest_x = size.width;
1340 this->smallest_y = size.height;
1341 this->fill_x = fill.width;
1342 this->fill_y = fill.height;
1343 this->resize_x = resize.width;
1344 this->resize_y = resize.height;
1345 this->ApplyAspectRatio();
1346 return;
1347 }
1348
1349 /* First sweep, recurse down and compute minimal size and filling. */
1350 this->smallest_x = 0;
1351 this->smallest_y = 0;
1352 this->fill_x = this->IsEmpty() ? 0 : 1;
1353 this->fill_y = this->IsEmpty() ? 0 : 1;
1354 this->resize_x = this->IsEmpty() ? 0 : 1;
1355 this->resize_y = this->IsEmpty() ? 0 : 1;
1356 for (const auto &child_wid : this->children) {
1357 child_wid->SetupSmallestSize(w);
1358
1359 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1360 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1361 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1362 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1363 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1364 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1365 this->ApplyAspectRatio();
1366 }
1367}
1368
1369void NWidgetStacked::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1370{
1371 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1372 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1373
1374 if (this->shown_plane >= SZSP_BEGIN) return;
1375
1376 for (const auto &child_wid : this->children) {
1377 uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1378 uint child_width = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1379 uint child_pos_x = (rtl ? child_wid->padding.right : child_wid->padding.left);
1380
1381 uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1382 uint child_height = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1383 uint child_pos_y = child_wid->padding.top;
1384
1385 child_wid->AssignSizePosition(sizing, x + child_pos_x, y + child_pos_y, child_width, child_height, rtl);
1386 }
1387}
1388
1390{
1391 /* We need to update widget_lookup later. */
1392 this->widget_lookup = &widget_lookup;
1393
1394 this->NWidgetContainer::FillWidgetLookup(widget_lookup);
1395 /* In case widget IDs are repeated, make sure Window::GetWidget works on displayed widgets. */
1396 if (static_cast<size_t>(this->shown_plane) < this->children.size()) this->children[shown_plane]->FillWidgetLookup(widget_lookup);
1397}
1398
1400{
1401 if (this->shown_plane >= SZSP_BEGIN) return;
1402
1403 assert(static_cast<size_t>(this->shown_plane) < this->children.size());
1404 this->children[shown_plane]->Draw(w);
1405 DrawOutline(w, this);
1406}
1407
1409{
1410 if (this->shown_plane >= SZSP_BEGIN) return nullptr;
1411
1412 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
1413
1414 if (static_cast<size_t>(this->shown_plane) >= this->children.size()) return nullptr;
1415 return this->children[shown_plane]->GetWidgetFromPos(x, y);
1416}
1417
1424{
1425 if (this->shown_plane == plane) return false;
1426 this->shown_plane = plane;
1427 /* In case widget IDs are repeated, make sure Window::GetWidget works on displayed widgets. */
1428 if (static_cast<size_t>(this->shown_plane) < this->children.size()) this->children[shown_plane]->FillWidgetLookup(*this->widget_lookup);
1429 return true;
1430}
1431
1432class NWidgetLayer : public NWidgetContainer {
1433public:
1434 NWidgetLayer(WidgetID index) : NWidgetContainer(NWID_LAYER, index) {}
1435
1436 void SetupSmallestSize(Window *w) override;
1437 void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override;
1438
1439 void Draw(const Window *w) override;
1440};
1441
1443{
1444 /* First sweep, recurse down and compute minimal size and filling. */
1445 this->smallest_x = 0;
1446 this->smallest_y = 0;
1447 this->fill_x = this->IsEmpty() ? 0 : 1;
1448 this->fill_y = this->IsEmpty() ? 0 : 1;
1449 this->resize_x = this->IsEmpty() ? 0 : 1;
1450 this->resize_y = this->IsEmpty() ? 0 : 1;
1451 for (const auto &child_wid : this->children) {
1452 child_wid->SetupSmallestSize(w);
1453
1454 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1455 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1456 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1457 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1458 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1459 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1460 this->ApplyAspectRatio();
1461 }
1462}
1463
1464void NWidgetLayer::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1465{
1466 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1467 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1468
1469 for (const auto &child_wid : this->children) {
1470 uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1471 uint child_width = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1472 uint child_pos_x = (rtl ? child_wid->padding.right : child_wid->padding.left);
1473
1474 uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1475 uint child_height = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1476 uint child_pos_y = child_wid->padding.top;
1477
1478 child_wid->AssignSizePosition(sizing, x + child_pos_x, y + child_pos_y, child_width, child_height, rtl);
1479 }
1480}
1481
1483{
1484 /* Draw in reverse order, as layers are arranged top-down. */
1485 for (auto it = std::rbegin(this->children); it != std::rend(this->children); ++it) {
1486 (*it)->Draw(w);
1487 }
1488
1489 DrawOutline(w, this);
1490}
1491
1499
1510{
1511 this->uz_pip_pre = pip_pre;
1512 this->uz_pip_inter = pip_inter;
1513 this->uz_pip_post = pip_post;
1514
1515 this->pip_pre = ScaleGUITrad(this->uz_pip_pre);
1516 this->pip_inter = ScaleGUITrad(this->uz_pip_inter);
1517 this->pip_post = ScaleGUITrad(this->uz_pip_post);
1518}
1519
1530{
1531 this->pip_ratio_pre = pip_ratio_pre;
1532 this->pip_ratio_inter = pip_ratio_inter;
1533 this->pip_ratio_post = pip_ratio_post;
1534}
1535
1537{
1538 this->smallest_x = 0; // Sum of minimal size of all children.
1539 this->smallest_y = 0; // Biggest child.
1540 this->fill_x = 0; // smallest non-zero child widget fill step.
1541 this->fill_y = 1; // smallest common child fill step.
1542 this->resize_x = 0; // smallest non-zero child widget resize step.
1543 this->resize_y = 1; // smallest common child resize step.
1544 this->gaps = 0;
1545
1546 /* 1a. Forward call, collect longest/widest child length. */
1547 uint longest = 0; // Longest child found.
1548 uint max_vert_fill = 0; // Biggest vertical fill step.
1549 for (const auto &child_wid : this->children) {
1550 child_wid->SetupSmallestSize(w);
1551 longest = std::max(longest, child_wid->smallest_x);
1552 max_vert_fill = std::max(max_vert_fill, child_wid->GetVerticalStepSize(ST_SMALLEST));
1553 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1554 if (child_wid->smallest_x != 0 || child_wid->fill_x != 0) this->gaps++;
1555 }
1556 if (this->gaps > 0) this->gaps--; // Number of gaps is number of widgets less one.
1557 /* 1b. Make the container higher if needed to accommodate all children nicely. */
1558 [[maybe_unused]] uint max_smallest = this->smallest_y + 3 * max_vert_fill; // Upper limit to computing smallest height.
1559 uint cur_height = this->smallest_y;
1560 for (;;) {
1561 for (const auto &child_wid : this->children) {
1562 uint step_size = child_wid->GetVerticalStepSize(ST_SMALLEST);
1563 uint child_height = child_wid->smallest_y + child_wid->padding.Vertical();
1564 if (step_size > 1 && child_height < cur_height) { // Small step sizes or already fitting children are not interesting.
1565 uint remainder = (cur_height - child_height) % step_size;
1566 if (remainder > 0) { // Child did not fit entirely, widen the container.
1567 cur_height += step_size - remainder;
1568 assert(cur_height < max_smallest); // Safeguard against infinite height expansion.
1569 /* Remaining children will adapt to the new cur_height, thus speeding up the computation. */
1570 }
1571 }
1572 }
1573 if (this->smallest_y == cur_height) break;
1574 this->smallest_y = cur_height; // Smallest height got changed, try again.
1575 }
1576 /* 2. For containers that must maintain equal width, extend child minimal size. */
1577 for (const auto &child_wid : this->children) {
1578 child_wid->smallest_y = this->smallest_y - child_wid->padding.Vertical();
1579 child_wid->ApplyAspectRatio();
1580 longest = std::max(longest, child_wid->smallest_x);
1581 }
1582 if (this->flags.Test(NWidContainerFlag::EqualSize)) {
1583 for (const auto &child_wid : this->children) {
1584 if (child_wid->fill_x == 1) child_wid->smallest_x = longest;
1585 }
1586 }
1587 /* 3. Compute smallest, fill, and resize values of the container. */
1588 for (const auto &child_wid : this->children) {
1589 this->smallest_x += child_wid->smallest_x + child_wid->padding.Horizontal();
1590 if (child_wid->fill_x > 0) {
1591 if (this->fill_x == 0 || this->fill_x > child_wid->fill_x) this->fill_x = child_wid->fill_x;
1592 }
1593 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1594
1595 if (child_wid->resize_x > 0) {
1596 if (this->resize_x == 0 || this->resize_x > child_wid->resize_x) this->resize_x = child_wid->resize_x;
1597 }
1598 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1599 }
1600 if (this->fill_x == 0 && this->pip_ratio_pre + this->pip_ratio_inter + this->pip_ratio_post > 0) this->fill_x = 1;
1601 /* 4. Increase by required PIP space. */
1602 this->smallest_x += this->pip_pre + this->gaps * this->pip_inter + this->pip_post;
1603}
1604
1605void NWidgetHorizontal::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1606{
1607 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1608
1609 /* Compute additional width given to us. */
1610 uint additional_length = given_width - (this->pip_pre + this->gaps * this->pip_inter + this->pip_post);
1611 for (const auto &child_wid : this->children) {
1612 if (child_wid->smallest_x != 0 || child_wid->fill_x != 0) additional_length -= child_wid->smallest_x + child_wid->padding.Horizontal();
1613 }
1614
1615 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1616
1617 /* In principle, the additional horizontal space is distributed evenly over the available resizable children. Due to step sizes, this may not always be feasible.
1618 * To make resizing work as good as possible, first children with biggest step sizes are done. These may get less due to rounding down.
1619 * 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
1620 * of the child with the smallest non-zero stepsize.
1621 *
1622 * 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
1623 * size and position, and directly call child->AssignSizePosition() with the computed values.
1624 * 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
1625 * then we call the child.
1626 */
1627
1628 /* First loop: Find biggest stepsize, find number of children that want a piece of the pie, handle vertical size for all children,
1629 * handle horizontal size for non-resizing children.
1630 */
1631 int num_changing_childs = 0; // Number of children that can change size.
1632 uint biggest_stepsize = 0;
1633 for (const auto &child_wid : this->children) {
1634 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1635 if (hor_step > 0) {
1636 if (!flags.Test(NWidContainerFlag::BigFirst)) num_changing_childs++;
1637 biggest_stepsize = std::max(biggest_stepsize, hor_step);
1638 } else {
1639 child_wid->current_x = child_wid->smallest_x;
1640 }
1641
1642 uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1643 child_wid->current_y = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1644 }
1645
1646 /* First.5 loop: count how many children are of the biggest step size. */
1647 if (flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1648 for (const auto &child_wid : this->children) {
1649 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1650 if (hor_step == biggest_stepsize) {
1651 num_changing_childs++;
1652 }
1653 }
1654 }
1655
1656 /* Second loop: Allocate the additional horizontal space over the resizing children, starting with the biggest resize steps. */
1657 while (biggest_stepsize > 0) {
1658 uint next_biggest_stepsize = 0;
1659 for (const auto &child_wid : this->children) {
1660 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1661 if (hor_step > biggest_stepsize) continue; // Already done
1662 if (hor_step == biggest_stepsize) {
1663 uint increment = additional_length / num_changing_childs;
1664 num_changing_childs--;
1665 if (hor_step > 1) increment -= increment % hor_step;
1666 child_wid->current_x = child_wid->smallest_x + increment;
1667 additional_length -= increment;
1668 continue;
1669 }
1670 next_biggest_stepsize = std::max(next_biggest_stepsize, hor_step);
1671 }
1672 biggest_stepsize = next_biggest_stepsize;
1673
1674 if (num_changing_childs == 0 && flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1675 /* Second.5 loop: count how many children are of the updated biggest step size. */
1676 for (const auto &child_wid : this->children) {
1677 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1678 if (hor_step == biggest_stepsize) {
1679 num_changing_childs++;
1680 }
1681 }
1682 }
1683 }
1684 assert(num_changing_childs == 0);
1685
1686 uint pre = this->pip_pre;
1687 uint inter = this->pip_inter;
1688
1689 if (additional_length > 0) {
1690 /* Allocate remaining space by pip ratios. If this doesn't round exactly, the unused space will fall into pip_post
1691 * which is never explicitly needed. */
1692 int r = this->pip_ratio_pre + this->gaps * this->pip_ratio_inter + this->pip_ratio_post;
1693 if (r > 0) {
1694 pre += this->pip_ratio_pre * additional_length / r;
1695 if (this->gaps > 0) inter += this->pip_ratio_inter * additional_length / r;
1696 }
1697 }
1698
1699 /* Third loop: Compute position and call the child. */
1700 uint position = rtl ? this->current_x - pre : pre; // Place to put next child relative to origin of the container.
1701 for (const auto &child_wid : this->children) {
1702 uint child_width = child_wid->current_x;
1703 uint child_x = x + (rtl ? position - child_width - child_wid->padding.left : position + child_wid->padding.left);
1704 uint child_y = y + child_wid->padding.top;
1705
1706 child_wid->AssignSizePosition(sizing, child_x, child_y, child_width, child_wid->current_y, rtl);
1707 if (child_wid->current_x != 0) {
1708 uint padded_child_width = child_width + child_wid->padding.Horizontal() + inter;
1709 position = rtl ? position - padded_child_width : position + padded_child_width;
1710 }
1711 }
1712}
1713
1714void NWidgetHorizontalLTR::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool)
1715{
1716 NWidgetHorizontal::AssignSizePosition(sizing, x, y, given_width, given_height, false);
1717}
1718
1720{
1721 this->smallest_x = 0; // Biggest child.
1722 this->smallest_y = 0; // Sum of minimal size of all children.
1723 this->fill_x = 1; // smallest common child fill step.
1724 this->fill_y = 0; // smallest non-zero child widget fill step.
1725 this->resize_x = 1; // smallest common child resize step.
1726 this->resize_y = 0; // smallest non-zero child widget resize step.
1727 this->gaps = 0;
1728
1729 /* 1a. Forward call, collect longest/widest child length. */
1730 uint highest = 0; // Highest child found.
1731 uint max_hor_fill = 0; // Biggest horizontal fill step.
1732 for (const auto &child_wid : this->children) {
1733 child_wid->SetupSmallestSize(w);
1734 highest = std::max(highest, child_wid->smallest_y);
1735 max_hor_fill = std::max(max_hor_fill, child_wid->GetHorizontalStepSize(ST_SMALLEST));
1736 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1737 if (child_wid->smallest_y != 0 || child_wid->fill_y != 0) this->gaps++;
1738 }
1739 if (this->gaps > 0) this->gaps--; // Number of gaps is number of widgets less one.
1740 /* 1b. Make the container wider if needed to accommodate all children nicely. */
1741 [[maybe_unused]] uint max_smallest = this->smallest_x + 3 * max_hor_fill; // Upper limit to computing smallest height.
1742 uint cur_width = this->smallest_x;
1743 for (;;) {
1744 for (const auto &child_wid : this->children) {
1745 uint step_size = child_wid->GetHorizontalStepSize(ST_SMALLEST);
1746 uint child_width = child_wid->smallest_x + child_wid->padding.Horizontal();
1747 if (step_size > 1 && child_width < cur_width) { // Small step sizes or already fitting children are not interesting.
1748 uint remainder = (cur_width - child_width) % step_size;
1749 if (remainder > 0) { // Child did not fit entirely, widen the container.
1750 cur_width += step_size - remainder;
1751 assert(cur_width < max_smallest); // Safeguard against infinite width expansion.
1752 /* Remaining children will adapt to the new cur_width, thus speeding up the computation. */
1753 }
1754 }
1755 }
1756 if (this->smallest_x == cur_width) break;
1757 this->smallest_x = cur_width; // Smallest width got changed, try again.
1758 }
1759 /* 2. For containers that must maintain equal width, extend children minimal size. */
1760 for (const auto &child_wid : this->children) {
1761 child_wid->smallest_x = this->smallest_x - child_wid->padding.Horizontal();
1762 child_wid->ApplyAspectRatio();
1763 highest = std::max(highest, child_wid->smallest_y);
1764 }
1765 if (this->flags.Test(NWidContainerFlag::EqualSize)) {
1766 for (const auto &child_wid : this->children) {
1767 if (child_wid->fill_y == 1) child_wid->smallest_y = highest;
1768 }
1769 }
1770 /* 3. Compute smallest, fill, and resize values of the container. */
1771 for (const auto &child_wid : this->children) {
1772 this->smallest_y += child_wid->smallest_y + child_wid->padding.Vertical();
1773 if (child_wid->fill_y > 0) {
1774 if (this->fill_y == 0 || this->fill_y > child_wid->fill_y) this->fill_y = child_wid->fill_y;
1775 }
1776 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1777
1778 if (child_wid->resize_y > 0) {
1779 if (this->resize_y == 0 || this->resize_y > child_wid->resize_y) this->resize_y = child_wid->resize_y;
1780 }
1781 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1782 }
1783 if (this->fill_y == 0 && this->pip_ratio_pre + this->pip_ratio_inter + this->pip_ratio_post > 0) this->fill_y = 1;
1784 /* 4. Increase by required PIP space. */
1785 this->smallest_y += this->pip_pre + this->gaps * this->pip_inter + this->pip_post;
1786}
1787
1788void NWidgetVertical::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1789{
1790 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1791
1792 /* Compute additional height given to us. */
1793 uint additional_length = given_height - (this->pip_pre + this->gaps * this->pip_inter + this->pip_post);
1794 for (const auto &child_wid : this->children) {
1795 if (child_wid->smallest_y != 0 || child_wid->fill_y != 0) additional_length -= child_wid->smallest_y + child_wid->padding.Vertical();
1796 }
1797
1798 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1799
1800 /* Like the horizontal container, the vertical container also distributes additional height evenly, starting with the children with the biggest resize steps.
1801 * It also stores computed widths and heights into current_x and current_y values of the child.
1802 */
1803
1804 /* 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. */
1805 int num_changing_childs = 0; // Number of children that can change size.
1806 uint biggest_stepsize = 0;
1807 for (const auto &child_wid : this->children) {
1808 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1809 if (vert_step > 0) {
1810 if (!flags.Test(NWidContainerFlag::BigFirst)) num_changing_childs++;
1811 biggest_stepsize = std::max(biggest_stepsize, vert_step);
1812 } else {
1813 child_wid->current_y = child_wid->smallest_y;
1814 }
1815
1816 uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1817 child_wid->current_x = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1818 }
1819
1820 /* First.5 loop: count how many children are of the biggest step size. */
1821 if (this->flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1822 for (const auto &child_wid : this->children) {
1823 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1824 if (vert_step == biggest_stepsize) {
1825 num_changing_childs++;
1826 }
1827 }
1828 }
1829
1830 /* Second loop: Allocate the additional vertical space over the resizing children, starting with the biggest resize steps. */
1831 while (biggest_stepsize > 0) {
1832 uint next_biggest_stepsize = 0;
1833 for (const auto &child_wid : this->children) {
1834 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1835 if (vert_step > biggest_stepsize) continue; // Already done
1836 if (vert_step == biggest_stepsize) {
1837 uint increment = additional_length / num_changing_childs;
1838 num_changing_childs--;
1839 if (vert_step > 1) increment -= increment % vert_step;
1840 child_wid->current_y = child_wid->smallest_y + increment;
1841 additional_length -= increment;
1842 continue;
1843 }
1844 next_biggest_stepsize = std::max(next_biggest_stepsize, vert_step);
1845 }
1846 biggest_stepsize = next_biggest_stepsize;
1847
1848 if (num_changing_childs == 0 && flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1849 /* Second.5 loop: count how many children are of the updated biggest step size. */
1850 for (const auto &child_wid : this->children) {
1851 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1852 if (vert_step == biggest_stepsize) {
1853 num_changing_childs++;
1854 }
1855 }
1856 }
1857 }
1858 assert(num_changing_childs == 0);
1859
1860 uint pre = this->pip_pre;
1861 uint inter = this->pip_inter;
1862
1863 if (additional_length > 0) {
1864 /* Allocate remaining space by pip ratios. If this doesn't round exactly, the unused space will fall into pip_post
1865 * which is never explicitly needed. */
1866 int r = this->pip_ratio_pre + this->gaps * this->pip_ratio_inter + this->pip_ratio_post;
1867 if (r > 0) {
1868 pre += this->pip_ratio_pre * additional_length / r;
1869 if (this->gaps > 0) inter += this->pip_ratio_inter * additional_length / r;
1870 }
1871 }
1872
1873 /* Third loop: Compute position and call the child. */
1874 uint position = pre; // Place to put next child relative to origin of the container.
1875 for (const auto &child_wid : this->children) {
1876 uint child_x = x + (rtl ? child_wid->padding.right : child_wid->padding.left);
1877 uint child_height = child_wid->current_y;
1878
1879 child_wid->AssignSizePosition(sizing, child_x, y + position + child_wid->padding.top, child_wid->current_x, child_height, rtl);
1880 if (child_wid->current_y != 0) {
1881 position += child_height + child_wid->padding.Vertical() + inter;
1882 }
1883 }
1884}
1885
1892{
1893 this->SetMinimalSize(width, height);
1894 this->SetResize(0, 0);
1895}
1896
1898{
1899 this->smallest_x = this->min_x;
1900 this->smallest_y = this->min_y;
1901 this->ApplyAspectRatio();
1902}
1903
1905{
1906 /* Spacer widget is never normally visible. */
1907
1908 if (_draw_widget_outlines && this->current_x != 0 && this->current_y != 0) {
1909 /* Spacers indicate a potential design issue, so get extra highlighting. */
1910 GfxFillRect(this->GetCurrentRect(), PC_WHITE, FILLRECT_CHECKER);
1911
1912 DrawOutline(w, this);
1913 }
1914}
1915
1917{
1918 /* Spacer widget never need repainting. */
1919}
1920
1922{
1923 return nullptr;
1924}
1925
1931{
1932 this->clicked = clicked;
1933 if (this->clicked >= 0 && this->sb != nullptr && this->widgets_x != 0) {
1934 int vpos = (this->clicked / this->widgets_x) * this->widget_h; // Vertical position of the top.
1935 /* Need to scroll down -> Scroll to the bottom.
1936 * However, last entry has no 'this->pip_inter' underneath, and we must stay below this->sb->GetCount() */
1937 if (this->sb->GetPosition() < vpos) vpos += this->widget_h - this->pip_inter - 1;
1938 this->sb->ScrollTowards(vpos);
1939 }
1940}
1941
1948{
1949 this->count = count;
1950
1951 if (this->sb == nullptr || this->widgets_x == 0) return;
1952
1953 /* We need to get the number of pixels the matrix is high/wide.
1954 * So, determine the number of rows/columns based on the number of
1955 * columns/rows (one is constant/unscrollable).
1956 * Then multiply that by the height of a widget, and add the pre
1957 * and post spacing "offsets". */
1958 count = CeilDiv(count, this->sb->IsVertical() ? this->widgets_x : this->widgets_y);
1959 count *= (this->sb->IsVertical() ? this->children.front()->smallest_y : this->children.front()->smallest_x) + this->pip_inter;
1960 if (count > 0) count -= this->pip_inter; // We counted an inter too much in the multiplication above
1961 count += this->pip_pre + this->pip_post;
1962 this->sb->SetCount(count);
1963 this->sb->SetCapacity(this->sb->IsVertical() ? this->current_y : this->current_x);
1964 this->sb->SetStepSize(this->sb->IsVertical() ? this->widget_h : this->widget_w);
1965}
1966
1972{
1973 this->sb = sb;
1974}
1975
1981{
1982 return this->current_element;
1983}
1984
1986{
1987 assert(this->children.size() == 1);
1988
1989 this->children.front()->SetupSmallestSize(w);
1990
1991 Dimension padding = { (uint)this->pip_pre + this->pip_post, (uint)this->pip_pre + this->pip_post};
1992 Dimension size = {this->children.front()->smallest_x + padding.width, this->children.front()->smallest_y + padding.height};
1993 Dimension fill = {0, 0};
1994 Dimension resize = {this->pip_inter + this->children.front()->smallest_x, this->pip_inter + this->children.front()->smallest_y};
1995
1996 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
1997
1998 this->smallest_x = size.width;
1999 this->smallest_y = size.height;
2000 this->fill_x = fill.width;
2001 this->fill_y = fill.height;
2002 this->resize_x = resize.width;
2003 this->resize_y = resize.height;
2004 this->ApplyAspectRatio();
2005}
2006
2007void NWidgetMatrix::AssignSizePosition(SizingType, int x, int y, uint given_width, uint given_height, bool)
2008{
2009 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
2010
2011 this->pos_x = x;
2012 this->pos_y = y;
2013 this->current_x = given_width;
2014 this->current_y = given_height;
2015
2016 /* Determine the size of the widgets, and the number of visible widgets on each of the axis. */
2017 this->widget_w = this->children.front()->smallest_x + this->pip_inter;
2018 this->widget_h = this->children.front()->smallest_y + this->pip_inter;
2019
2020 /* Account for the pip_inter is between widgets, so we need to account for that when
2021 * the division assumes pip_inter is used for all widgets. */
2022 this->widgets_x = CeilDiv(this->current_x - this->pip_pre - this->pip_post + this->pip_inter, this->widget_w);
2023 this->widgets_y = CeilDiv(this->current_y - this->pip_pre - this->pip_post + this->pip_inter, this->widget_h);
2024
2025 /* When resizing, update the scrollbar's count. E.g. with a vertical
2026 * scrollbar becoming wider or narrower means the amount of rows in
2027 * the scrollbar becomes respectively smaller or higher. */
2028 this->SetCount(this->count);
2029}
2030
2032{
2033 /* Falls outside of the matrix widget. */
2034 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
2035
2036 int start_x, start_y, base_offs_x, base_offs_y;
2037 this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
2038
2039 bool rtl = _current_text_dir == TD_RTL;
2040
2041 int widget_col = (rtl ?
2042 -x + (int)this->pip_post + this->pos_x + base_offs_x + this->widget_w - 1 - (int)this->pip_inter :
2043 x - (int)this->pip_pre - this->pos_x - base_offs_x
2044 ) / this->widget_w;
2045
2046 int widget_row = (y - base_offs_y - (int)this->pip_pre - this->pos_y) / this->widget_h;
2047
2048 this->current_element = (widget_row + start_y) * this->widgets_x + start_x + widget_col;
2049 if (this->current_element >= this->count) return nullptr;
2050
2051 NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->children.front().get());
2052 assert(child != nullptr);
2054 this->pos_x + (rtl ? this->pip_post - widget_col * this->widget_w : this->pip_pre + widget_col * this->widget_w) + base_offs_x,
2055 this->pos_y + this->pip_pre + widget_row * this->widget_h + base_offs_y,
2056 child->smallest_x, child->smallest_y, rtl);
2057
2058 return child->GetWidgetFromPos(x, y);
2059}
2060
2061/* virtual */ void NWidgetMatrix::Draw(const Window *w)
2062{
2063 /* Fill the background. */
2064 GfxFillRect(this->GetCurrentRect(), GetColourGradient(this->colour, SHADE_LIGHT));
2065
2066 /* Set up a clipping area for the previews. */
2067 bool rtl = _current_text_dir == TD_RTL;
2068 DrawPixelInfo tmp_dpi;
2069 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;
2070
2071 {
2072 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
2073
2074 /* Get the appropriate offsets so we can draw the right widgets. */
2075 NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->children.front().get());
2076 assert(child != nullptr);
2077 int start_x, start_y, base_offs_x, base_offs_y;
2078 this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
2079
2080 int offs_y = base_offs_y;
2081 for (int y = start_y; y < start_y + this->widgets_y + 1; y++, offs_y += this->widget_h) {
2082 /* Are we within bounds? */
2083 if (offs_y + child->smallest_y <= 0) continue;
2084 if (offs_y >= (int)this->current_y) break;
2085
2086 /* We've passed our amount of widgets. */
2087 if (y * this->widgets_x >= this->count) break;
2088
2089 int offs_x = base_offs_x;
2090 for (int x = start_x; x < start_x + this->widgets_x + 1; x++, offs_x += rtl ? -this->widget_w : this->widget_w) {
2091 /* Are we within bounds? */
2092 if (offs_x + child->smallest_x <= 0) continue;
2093 if (offs_x >= (int)this->current_x) continue;
2094
2095 /* Do we have this many widgets? */
2096 this->current_element = y * this->widgets_x + x;
2097 if (this->current_element >= this->count) break;
2098
2099 child->AssignSizePosition(ST_RESIZE, offs_x, offs_y, child->smallest_x, child->smallest_y, rtl);
2100 child->SetLowered(this->clicked == this->current_element);
2101 child->Draw(w);
2102 }
2103 }
2104 }
2105
2106 DrawOutline(w, this);
2107}
2108
2116void NWidgetMatrix::GetScrollOffsets(int &start_x, int &start_y, int &base_offs_x, int &base_offs_y)
2117{
2118 base_offs_x = _current_text_dir == TD_RTL ? this->widget_w * (this->widgets_x - 1) : 0;
2119 base_offs_y = 0;
2120 start_x = 0;
2121 start_y = 0;
2122 if (this->sb != nullptr) {
2123 if (this->sb->IsVertical()) {
2124 start_y = this->sb->GetPosition() / this->widget_h;
2125 base_offs_y += -this->sb->GetPosition() + start_y * this->widget_h;
2126 } else {
2127 start_x = this->sb->GetPosition() / this->widget_w;
2128 int sub_x = this->sb->GetPosition() - start_x * this->widget_w;
2129 if (_current_text_dir == TD_RTL) {
2130 base_offs_x += sub_x;
2131 } else {
2132 base_offs_x -= sub_x;
2133 }
2134 }
2135 }
2136}
2137
2147NWidgetBackground::NWidgetBackground(WidgetType tp, Colours colour, WidgetID index, std::unique_ptr<NWidgetPIPContainer> &&child) : NWidgetCore(tp, colour, index, 1, 1, {}, STR_NULL)
2148{
2149 assert(tp == WWT_PANEL || tp == WWT_INSET || tp == WWT_FRAME);
2150 this->child = std::move(child);
2151 if (this->child != nullptr) this->child->parent = this;
2152 this->SetAlignment(SA_TOP | SA_LEFT);
2153}
2154
2162void NWidgetBackground::Add(std::unique_ptr<NWidgetBase> &&nwid)
2163{
2164 if (this->child == nullptr) {
2165 this->child = std::make_unique<NWidgetVertical>();
2166 }
2167 nwid->parent = this->child.get();
2168 this->child->Add(std::move(nwid));
2169}
2170
2181void NWidgetBackground::SetPIP(uint8_t pip_pre, uint8_t pip_inter, uint8_t pip_post)
2182{
2183 if (this->child == nullptr) {
2184 this->child = std::make_unique<NWidgetVertical>();
2185 }
2186 this->child->parent = this;
2187 this->child->SetPIP(pip_pre, pip_inter, pip_post);
2188}
2189
2200void NWidgetBackground::SetPIPRatio(uint8_t pip_ratio_pre, uint8_t pip_ratio_inter, uint8_t pip_ratio_post)
2201{
2202 if (this->child == nullptr) {
2203 this->child = std::make_unique<NWidgetVertical>();
2204 }
2205 this->child->parent = this;
2206 this->child->SetPIPRatio(pip_ratio_pre, pip_ratio_inter, pip_ratio_post);
2207}
2208
2210{
2211 if (child != nullptr) child->AdjustPaddingForZoom();
2213}
2214
2216{
2217 if (this->child != nullptr) {
2218 this->child->SetupSmallestSize(w);
2219
2220 this->smallest_x = this->child->smallest_x;
2221 this->smallest_y = this->child->smallest_y;
2222 this->fill_x = this->child->fill_x;
2223 this->fill_y = this->child->fill_y;
2224 this->resize_x = this->child->resize_x;
2225 this->resize_y = this->child->resize_y;
2226
2227 /* Don't apply automatic padding if there is no child widget. */
2228 if (w == nullptr) return;
2229
2230 if (this->type == WWT_FRAME) {
2231 std::string text = GetStringForWidget(w, this);
2232 Dimension text_size = text.empty() ? Dimension{0, 0} : GetStringBoundingBox(text, this->text_size);
2233
2234 /* Account for the size of the frame's text if that exists */
2235 this->child->padding = WidgetDimensions::scaled.frametext;
2236 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);
2237
2238 this->smallest_x += this->child->padding.Horizontal();
2239 this->smallest_y += this->child->padding.Vertical();
2240
2241 this->smallest_x = std::max(this->smallest_x, text_size.width + WidgetDimensions::scaled.frametext.Horizontal());
2242 } else if (this->type == WWT_INSET) {
2243 /* Apply automatic padding for bevel thickness. */
2244 this->child->padding = WidgetDimensions::scaled.bevel;
2245
2246 this->smallest_x += this->child->padding.Horizontal();
2247 this->smallest_y += this->child->padding.Vertical();
2248 }
2249 this->ApplyAspectRatio();
2250 } else {
2251 Dimension d = {this->min_x, this->min_y};
2252 Dimension fill = {this->fill_x, this->fill_y};
2253 Dimension resize = {this->resize_x, this->resize_y};
2254 if (w != nullptr) { // A non-nullptr window pointer acts as switch to turn dynamic widget size on.
2255 if (this->type == WWT_FRAME || this->type == WWT_INSET) {
2256 std::string text = GetStringForWidget(w, this);
2257 if (!text.empty()) {
2258 Dimension background = GetStringBoundingBox(text, this->text_size);
2259 background.width += (this->type == WWT_FRAME) ? (WidgetDimensions::scaled.frametext.Horizontal()) : (WidgetDimensions::scaled.inset.Horizontal());
2260 d = maxdim(d, background);
2261 }
2262 }
2263 if (this->index >= 0) {
2265 switch (this->type) {
2266 default: NOT_REACHED();
2267 case WWT_PANEL: padding = {WidgetDimensions::scaled.framerect.Horizontal(), WidgetDimensions::scaled.framerect.Vertical()}; break;
2268 case WWT_FRAME: padding = {WidgetDimensions::scaled.frametext.Horizontal(), WidgetDimensions::scaled.frametext.Vertical()}; break;
2269 case WWT_INSET: padding = {WidgetDimensions::scaled.inset.Horizontal(), WidgetDimensions::scaled.inset.Vertical()}; break;
2270 }
2271 w->UpdateWidgetSize(this->index, d, padding, fill, resize);
2272 }
2273 }
2274 this->smallest_x = d.width;
2275 this->smallest_y = d.height;
2276 this->fill_x = fill.width;
2277 this->fill_y = fill.height;
2278 this->resize_x = resize.width;
2279 this->resize_y = resize.height;
2280 this->ApplyAspectRatio();
2281 }
2282}
2283
2284void NWidgetBackground::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
2285{
2286 this->StoreSizePosition(sizing, x, y, given_width, given_height);
2287
2288 if (this->child != nullptr) {
2289 uint x_offset = (rtl ? this->child->padding.right : this->child->padding.left);
2290 uint width = given_width - this->child->padding.Horizontal();
2291 uint height = given_height - this->child->padding.Vertical();
2292 this->child->AssignSizePosition(sizing, x + x_offset, y + this->child->padding.top, width, height, rtl);
2293 }
2294}
2295
2297{
2298 this->NWidgetCore::FillWidgetLookup(widget_lookup);
2299 if (this->child != nullptr) this->child->FillWidgetLookup(widget_lookup);
2300}
2301
2303{
2304 if (this->current_x == 0 || this->current_y == 0) return;
2305
2306 Rect r = this->GetCurrentRect();
2307
2308 const DrawPixelInfo *dpi = _cur_dpi;
2309 if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2310
2311 switch (this->type) {
2312 case WWT_PANEL:
2313 DrawFrameRect(r, this->colour, this->IsLowered() ? FrameFlag::Lowered : FrameFlags{});
2314 break;
2315
2316 case WWT_FRAME:
2317 DrawFrame(r, this->colour, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
2318 break;
2319
2320 case WWT_INSET:
2321 DrawInset(r, this->colour, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
2322 break;
2323
2324 default:
2325 NOT_REACHED();
2326 }
2327
2328 if (this->index >= 0) w->DrawWidget(r, this->index);
2329 if (this->child != nullptr) this->child->Draw(w);
2330
2331 if (this->IsDisabled()) {
2333 }
2334
2335 DrawOutline(w, this);
2336}
2337
2339{
2340 NWidgetCore *nwid = nullptr;
2341 if (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) {
2342 if (this->child != nullptr) nwid = this->child->GetWidgetFromPos(x, y);
2343 if (nwid == nullptr) nwid = this;
2344 }
2345 return nwid;
2346}
2347
2349{
2350 NWidgetBase *nwid = nullptr;
2351 if (this->child != nullptr) nwid = this->child->GetWidgetOfType(tp);
2352 if (nwid == nullptr && this->type == tp) nwid = this;
2353 return nwid;
2354}
2355
2356NWidgetViewport::NWidgetViewport(WidgetID index) : NWidgetCore(NWID_VIEWPORT, INVALID_COLOUR, index, 1, 1, {}, STR_NULL)
2357{
2358}
2359
2361{
2362 this->smallest_x = this->min_x;
2363 this->smallest_y = this->min_y;
2364 this->ApplyAspectRatio();
2365}
2366
2368{
2369 if (this->current_x == 0 || this->current_y == 0) return;
2370
2373 _transparency_opt &= (1 << TO_SIGNS) | (1 << TO_TEXT); // Disable all transparency, except textual stuff
2374 w->DrawViewport();
2375 _transparency_opt = to_backup;
2376 } else {
2377 w->DrawViewport();
2378 }
2379
2380 /* Optionally shade the viewport. */
2381 if (this->disp_flags.Any({NWidgetDisplayFlag::ShadeGrey, NWidgetDisplayFlag::ShadeDimmed})) {
2383 }
2384
2385 DrawOutline(w, this);
2386}
2387
2394void NWidgetViewport::InitializeViewport(Window *w, std::variant<TileIndex, VehicleID> focus, ZoomLevel zoom)
2395{
2396 InitializeWindowViewport(w, this->pos_x, this->pos_y, this->current_x, this->current_y, focus, zoom);
2397}
2398
2404{
2405 if (w->viewport == nullptr) return;
2406
2407 Viewport &vp = *w->viewport;
2408 vp.left = w->left + this->pos_x;
2409 vp.top = w->top + this->pos_y;
2410 vp.width = this->current_x;
2411 vp.height = this->current_y;
2412
2413 vp.virtual_width = ScaleByZoom(vp.width, vp.zoom);
2415}
2416
2426Scrollbar::size_type Scrollbar::GetScrolledRowFromWidget(int clickpos, const Window * const w, WidgetID widget, int padding, int line_height) const
2427{
2428 int pos = w->GetRowFromWidget(clickpos, widget, padding, line_height);
2429 if (pos != INT_MAX) pos += this->GetPosition();
2430 return (pos < 0 || pos >= this->GetCount()) ? Scrollbar::npos : pos;
2431}
2432
2447EventState Scrollbar::UpdateListPositionOnKeyPress(int &list_position, uint16_t keycode) const
2448{
2449 int new_pos = list_position;
2450 switch (keycode) {
2451 case WKC_UP:
2452 /* scroll up by one */
2453 new_pos--;
2454 break;
2455
2456 case WKC_DOWN:
2457 /* scroll down by one */
2458 new_pos++;
2459 break;
2460
2461 case WKC_PAGEUP:
2462 /* scroll up a page */
2463 new_pos -= this->GetCapacity();
2464 break;
2465
2466 case WKC_PAGEDOWN:
2467 /* scroll down a page */
2468 new_pos += this->GetCapacity();
2469 break;
2470
2471 case WKC_HOME:
2472 /* jump to beginning */
2473 new_pos = 0;
2474 break;
2475
2476 case WKC_END:
2477 /* jump to end */
2478 new_pos = this->GetCount() - 1;
2479 break;
2480
2481 default:
2482 return ES_NOT_HANDLED;
2483 }
2484
2485 /* If there are no elements, there is nothing to scroll/update. */
2486 if (this->GetCount() != 0) {
2487 list_position = Clamp(new_pos, 0, this->GetCount() - 1);
2488 }
2489 return ES_HANDLED;
2490}
2491
2492
2501{
2502 NWidgetBase *nwid = w->GetWidget<NWidgetBase>(widget);
2503 if (this->IsVertical()) {
2504 this->SetCapacity(((int)nwid->current_y - padding) / (int)nwid->resize_y);
2505 } else {
2506 this->SetCapacity(((int)nwid->current_x - padding) / (int)nwid->resize_x);
2507 }
2508}
2509
2517Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
2518{
2519 const int count = sb.GetCount() * resize_step;
2520 const int position = sb.GetPosition() * resize_step;
2521
2522 if (sb.IsVertical()) {
2523 r.top -= position;
2524 r.bottom = r.top + count;
2525 } else {
2526 bool rtl = _current_text_dir == TD_RTL;
2527 if (rtl) {
2528 r.right += position;
2529 r.left = r.right - count;
2530 } else {
2531 r.left -= position;
2532 r.right = r.left + count;
2533 }
2534 }
2535
2536 return r;
2537}
2538
2546{
2547 assert(tp == NWID_HSCROLLBAR || tp == NWID_VSCROLLBAR);
2548
2549 switch (this->type) {
2550 case NWID_HSCROLLBAR:
2551 this->SetResize(1, 0);
2552 this->SetFill(1, 0);
2553 this->SetToolTip(STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST);
2554 break;
2555
2556 case NWID_VSCROLLBAR:
2557 this->SetResize(0, 1);
2558 this->SetFill(0, 1);
2559 this->SetToolTip(STR_TOOLTIP_VSCROLL_BAR_SCROLLS_LIST);
2560 break;
2561
2562 default: NOT_REACHED();
2563 }
2564}
2565
2567{
2568 this->min_x = 0;
2569 this->min_y = 0;
2570
2571 switch (this->type) {
2572 case NWID_HSCROLLBAR:
2573 this->SetMinimalSizeAbsolute(NWidgetScrollbar::GetHorizontalDimension().width * 3, NWidgetScrollbar::GetHorizontalDimension().height);
2574 break;
2575
2576 case NWID_VSCROLLBAR:
2577 this->SetMinimalSizeAbsolute(NWidgetScrollbar::GetVerticalDimension().width, NWidgetScrollbar::GetVerticalDimension().height * 3);
2578 break;
2579
2580 default: NOT_REACHED();
2581 }
2582
2583 this->smallest_x = this->min_x;
2584 this->smallest_y = this->min_y;
2585}
2586
2588{
2589 if (this->current_x == 0 || this->current_y == 0) return;
2590
2591 Rect r = this->GetCurrentRect();
2592
2593 const DrawPixelInfo *dpi = _cur_dpi;
2594 if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2595
2596 bool up_lowered = this->disp_flags.Test(NWidgetDisplayFlag::ScrollbarUp);
2597 bool down_lowered = this->disp_flags.Test(NWidgetDisplayFlag::ScrollbarDown);
2598 bool middle_lowered = !this->disp_flags.Any({NWidgetDisplayFlag::ScrollbarUp, NWidgetDisplayFlag::ScrollbarDown}) && w->mouse_capture_widget == this->index;
2599
2600 if (this->type == NWID_HSCROLLBAR) {
2601 DrawHorizontalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2602 } else {
2603 DrawVerticalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2604 }
2605
2606 if (this->IsDisabled()) {
2608 }
2609
2610 DrawOutline(w, this);
2611}
2612
2613/* static */ void NWidgetScrollbar::InvalidateDimensionCache()
2614{
2615 vertical_dimension.width = vertical_dimension.height = 0;
2616 horizontal_dimension.width = horizontal_dimension.height = 0;
2617}
2618
2619/* static */ Dimension NWidgetScrollbar::GetVerticalDimension()
2620{
2621 if (vertical_dimension.width == 0) {
2622 vertical_dimension = maxdim(GetScaledSpriteSize(SPR_ARROW_UP), GetScaledSpriteSize(SPR_ARROW_DOWN));
2625 }
2626 return vertical_dimension;
2627}
2628
2629/* static */ Dimension NWidgetScrollbar::GetHorizontalDimension()
2630{
2631 if (horizontal_dimension.width == 0) {
2632 horizontal_dimension = maxdim(GetScaledSpriteSize(SPR_ARROW_LEFT), GetScaledSpriteSize(SPR_ARROW_RIGHT));
2635 }
2636 return horizontal_dimension;
2637}
2638
2641
2644{
2645 shadebox_dimension.width = shadebox_dimension.height = 0;
2646 debugbox_dimension.width = debugbox_dimension.height = 0;
2647 defsizebox_dimension.width = defsizebox_dimension.height = 0;
2648 stickybox_dimension.width = stickybox_dimension.height = 0;
2649 resizebox_dimension.width = resizebox_dimension.height = 0;
2650 closebox_dimension.width = closebox_dimension.height = 0;
2651 dropdown_dimension.width = dropdown_dimension.height = 0;
2652}
2653
2661
2670NWidgetLeaf::NWidgetLeaf(WidgetType tp, Colours colour, WidgetID index, const WidgetData &data, StringID tip) : NWidgetCore(tp, colour, index, 1, 1, data, tip)
2671{
2672 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);
2673 this->min_x = 0;
2674 this->min_y = 0;
2675 this->SetResize(0, 0);
2676
2677 switch (tp) {
2678 case WWT_EMPTY:
2679 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_EMPTY should not have a colour");
2680 break;
2681
2682 case WWT_TEXT:
2683 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_TEXT should not have a colour");
2684 this->SetFill(0, 0);
2686 break;
2687
2688 case WWT_LABEL:
2689 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_LABEL should not have a colour");
2690 [[fallthrough]];
2691
2692 case WWT_PUSHBTN:
2693 case WWT_IMGBTN:
2694 case WWT_PUSHIMGBTN:
2695 case WWT_IMGBTN_2:
2696 case WWT_TEXTBTN:
2697 case WWT_PUSHTXTBTN:
2698 case WWT_TEXTBTN_2:
2699 case WWT_IMGTEXTBTN:
2700 case WWT_PUSHIMGTEXTBTN:
2701 case WWT_BOOLBTN:
2702 case WWT_MATRIX:
2704 case NWID_PUSHBUTTON_DROPDOWN:
2705 this->SetFill(0, 0);
2706 break;
2707
2708 case WWT_ARROWBTN:
2709 case WWT_PUSHARROWBTN:
2710 this->SetFill(0, 0);
2711 this->SetAspect(WidgetDimensions::ASPECT_LEFT_RIGHT_BUTTON);
2712 break;
2713
2714 case WWT_EDITBOX:
2715 this->SetFill(0, 0);
2716 break;
2717
2718 case WWT_CAPTION:
2719 this->SetFill(1, 0);
2720 this->SetResize(1, 0);
2722 this->SetMinimalTextLines(1, WidgetDimensions::unscaled.captiontext.Vertical(), FS_NORMAL);
2723 this->SetToolTip(STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
2724 break;
2725
2726 case WWT_STICKYBOX:
2727 this->SetFill(0, 0);
2729 this->SetToolTip(STR_TOOLTIP_STICKY);
2730 this->SetAspect(this->min_x, this->min_y);
2731 break;
2732
2733 case WWT_SHADEBOX:
2734 this->SetFill(0, 0);
2736 this->SetToolTip(STR_TOOLTIP_SHADE);
2737 this->SetAspect(this->min_x, this->min_y);
2738 break;
2739
2740 case WWT_DEBUGBOX:
2741 this->SetFill(0, 0);
2743 this->SetToolTip(STR_TOOLTIP_DEBUG);
2744 this->SetAspect(this->min_x, this->min_y);
2745 break;
2746
2747 case WWT_DEFSIZEBOX:
2748 this->SetFill(0, 0);
2750 this->SetToolTip(STR_TOOLTIP_DEFSIZE);
2751 this->SetAspect(this->min_x, this->min_y);
2752 break;
2753
2754 case WWT_RESIZEBOX:
2755 this->SetFill(0, 0);
2758 this->SetToolTip(STR_TOOLTIP_RESIZE);
2759 break;
2760
2761 case WWT_CLOSEBOX:
2762 this->SetFill(0, 0);
2764 this->SetToolTip(STR_TOOLTIP_CLOSE_WINDOW);
2765 this->SetAspect(this->min_x, this->min_y);
2766 break;
2767
2768 case WWT_DROPDOWN:
2769 this->SetFill(0, 0);
2771 this->SetAlignment(SA_TOP | SA_LEFT);
2772 break;
2773
2774 default:
2775 NOT_REACHED();
2776 }
2777}
2778
2780{
2781 Dimension padding = {0, 0};
2782 Dimension size = {this->min_x, this->min_y};
2783 Dimension fill = {this->fill_x, this->fill_y};
2784 Dimension resize = {this->resize_x, this->resize_y};
2785 switch (this->type) {
2786 case WWT_EMPTY: {
2787 break;
2788 }
2789 case WWT_MATRIX: {
2790 padding = {WidgetDimensions::scaled.matrix.Horizontal(), WidgetDimensions::scaled.matrix.Vertical()};
2791 break;
2792 }
2793 case WWT_SHADEBOX: {
2794 padding = {WidgetDimensions::scaled.shadebox.Horizontal(), WidgetDimensions::scaled.shadebox.Vertical()};
2795 if (NWidgetLeaf::shadebox_dimension.width == 0) {
2796 NWidgetLeaf::shadebox_dimension = maxdim(GetScaledSpriteSize(SPR_WINDOW_SHADE), GetScaledSpriteSize(SPR_WINDOW_UNSHADE));
2799 }
2801 break;
2802 }
2803 case WWT_DEBUGBOX:
2804 if (_settings_client.gui.newgrf_developer_tools && w->IsNewGRFInspectable()) {
2805 padding = {WidgetDimensions::scaled.debugbox.Horizontal(), WidgetDimensions::scaled.debugbox.Vertical()};
2806 if (NWidgetLeaf::debugbox_dimension.width == 0) {
2810 }
2812 } else {
2813 /* If the setting is disabled we don't want to see it! */
2814 size.width = 0;
2815 fill.width = 0;
2816 resize.width = 0;
2817 }
2818 break;
2819
2820 case WWT_STICKYBOX: {
2821 padding = {WidgetDimensions::scaled.stickybox.Horizontal(), WidgetDimensions::scaled.stickybox.Vertical()};
2822 if (NWidgetLeaf::stickybox_dimension.width == 0) {
2826 }
2828 break;
2829 }
2830
2831 case WWT_DEFSIZEBOX: {
2832 padding = {WidgetDimensions::scaled.defsizebox.Horizontal(), WidgetDimensions::scaled.defsizebox.Vertical()};
2833 if (NWidgetLeaf::defsizebox_dimension.width == 0) {
2837 }
2839 break;
2840 }
2841
2842 case WWT_RESIZEBOX: {
2843 padding = {WidgetDimensions::scaled.resizebox.Horizontal(), WidgetDimensions::scaled.resizebox.Vertical()};
2844 if (NWidgetLeaf::resizebox_dimension.width == 0) {
2845 NWidgetLeaf::resizebox_dimension = maxdim(GetScaledSpriteSize(SPR_WINDOW_RESIZE_LEFT), GetScaledSpriteSize(SPR_WINDOW_RESIZE_RIGHT));
2848 }
2850 break;
2851 }
2852 case WWT_EDITBOX: {
2853 Dimension sprite_size = GetScaledSpriteSize(_current_text_dir == TD_RTL ? SPR_IMG_DELETE_RIGHT : SPR_IMG_DELETE_LEFT);
2854 size.width = std::max(size.width, ScaleGUITrad(30) + sprite_size.width);
2855 size.height = std::max(sprite_size.height, GetStringBoundingBox("_").height + WidgetDimensions::scaled.framerect.Vertical());
2856 }
2857 [[fallthrough]];
2858 case WWT_PUSHBTN: {
2859 padding = {WidgetDimensions::scaled.frametext.Horizontal(), WidgetDimensions::scaled.framerect.Vertical()};
2860 break;
2861 }
2862
2863 case WWT_BOOLBTN:
2864 size.width = SETTING_BUTTON_WIDTH;
2865 size.height = SETTING_BUTTON_HEIGHT;
2866 break;
2867
2868 case WWT_IMGBTN:
2869 case WWT_IMGBTN_2:
2870 case WWT_PUSHIMGBTN: {
2871 padding = {WidgetDimensions::scaled.imgbtn.Horizontal(), WidgetDimensions::scaled.imgbtn.Vertical()};
2872 Dimension d2 = GetScaledSpriteSize(this->widget_data.sprite);
2873 if (this->type == WWT_IMGBTN_2) d2 = maxdim(d2, GetScaledSpriteSize(this->widget_data.sprite + 1));
2874 d2.width += padding.width;
2875 d2.height += padding.height;
2876 size = maxdim(size, d2);
2877 break;
2878 }
2879
2880 case WWT_IMGTEXTBTN:
2881 case WWT_PUSHIMGTEXTBTN: {
2882 padding = {WidgetDimensions::scaled.framerect.Horizontal(), WidgetDimensions::scaled.framerect.Vertical()};
2883 Dimension di = GetScaledSpriteSize(this->widget_data.sprite);
2884 Dimension dt = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2885 Dimension d2{
2886 padding.width + di.width + WidgetDimensions::scaled.hsep_wide + dt.width,
2887 padding.height + std::max(di.height, dt.height)
2888 };
2889 size = maxdim(size, d2);
2890 break;
2891 }
2892
2893 case WWT_ARROWBTN:
2894 case WWT_PUSHARROWBTN: {
2895 padding = {WidgetDimensions::scaled.imgbtn.Horizontal(), WidgetDimensions::scaled.imgbtn.Vertical()};
2896 Dimension d2 = maxdim(GetScaledSpriteSize(SPR_ARROW_LEFT), GetScaledSpriteSize(SPR_ARROW_RIGHT));
2897 d2.width += padding.width;
2898 d2.height += padding.height;
2899 size = maxdim(size, d2);
2900 break;
2901 }
2902
2903 case WWT_CLOSEBOX: {
2904 padding = {WidgetDimensions::scaled.closebox.Horizontal(), WidgetDimensions::scaled.closebox.Vertical()};
2905 if (NWidgetLeaf::closebox_dimension.width == 0) {
2909 }
2911 break;
2912 }
2913 case WWT_TEXTBTN:
2914 case WWT_PUSHTXTBTN:
2915 case WWT_TEXTBTN_2: {
2916 padding = {WidgetDimensions::scaled.framerect.Horizontal(), WidgetDimensions::scaled.framerect.Vertical()};
2917 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2918 d2.width += padding.width;
2919 d2.height += padding.height;
2920 size = maxdim(size, d2);
2921 break;
2922 }
2923 case WWT_LABEL:
2924 case WWT_TEXT: {
2925 size = maxdim(size, GetStringBoundingBox(GetStringForWidget(w, this), this->text_size));
2926 break;
2927 }
2928 case WWT_CAPTION: {
2929 padding = {WidgetDimensions::scaled.captiontext.Horizontal(), WidgetDimensions::scaled.captiontext.Vertical()};
2930 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2931 d2.width += padding.width;
2932 d2.height += padding.height;
2933 size = maxdim(size, d2);
2934 break;
2935 }
2936 case WWT_DROPDOWN:
2938 case NWID_PUSHBUTTON_DROPDOWN: {
2939 if (NWidgetLeaf::dropdown_dimension.width == 0) {
2941 NWidgetLeaf::dropdown_dimension.width += WidgetDimensions::scaled.vscrollbar.Horizontal();
2942 NWidgetLeaf::dropdown_dimension.height += WidgetDimensions::scaled.vscrollbar.Vertical();
2943 }
2944 padding = {WidgetDimensions::scaled.dropdowntext.Horizontal() + NWidgetLeaf::dropdown_dimension.width + WidgetDimensions::scaled.fullbevel.Horizontal(), WidgetDimensions::scaled.dropdowntext.Vertical()};
2945 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2946 d2.width += padding.width;
2947 d2.height = std::max(d2.height + padding.height, NWidgetLeaf::dropdown_dimension.height);
2948 size = maxdim(size, d2);
2949 break;
2950 }
2951 default:
2952 NOT_REACHED();
2953 }
2954
2955 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
2956
2957 this->smallest_x = size.width;
2958 this->smallest_y = size.height;
2959 this->fill_x = fill.width;
2960 this->fill_y = fill.height;
2961 this->resize_x = resize.width;
2962 this->resize_y = resize.height;
2963 this->ApplyAspectRatio();
2964}
2965
2967{
2968 if (this->current_x == 0 || this->current_y == 0) return;
2969
2970 /* Setup a clipping rectangle... for WWT_EMPTY or WWT_TEXT, an extra scaled pixel is allowed in case text shadow encroaches. */
2971 int extra = (this->type == WWT_EMPTY || this->type == WWT_TEXT) ? ScaleGUITrad(1) : 0;
2972 DrawPixelInfo new_dpi;
2973 if (!FillDrawPixelInfo(&new_dpi, this->pos_x, this->pos_y, this->current_x + extra, this->current_y + extra)) return;
2974 /* ...but keep coordinates relative to the window. */
2975 new_dpi.left += this->pos_x;
2976 new_dpi.top += this->pos_y;
2977
2978 AutoRestoreBackup dpi_backup(_cur_dpi, &new_dpi);
2979
2980 Rect r = this->GetCurrentRect();
2981
2982 bool clicked = this->IsLowered();
2983 switch (this->type) {
2984 case WWT_EMPTY:
2985 /* WWT_EMPTY used as a spacer indicates a potential design issue. */
2986 if (this->index == -1 && _draw_widget_outlines) {
2988 }
2989 break;
2990
2991 case WWT_PUSHBTN:
2992 DrawFrameRect(r, this->colour, clicked ? FrameFlag::Lowered : FrameFlags{});
2993 break;
2994
2995 case WWT_BOOLBTN: {
2997 Colours button_colour = this->widget_data.alternate_colour;
2998 if (button_colour == INVALID_COLOUR) button_colour = this->colour;
2999 DrawBoolButton(pt.x, pt.y, button_colour, this->colour, clicked, !this->IsDisabled());
3000 break;
3001 }
3002
3003 case WWT_IMGBTN:
3004 case WWT_PUSHIMGBTN:
3005 case WWT_IMGBTN_2:
3006 DrawImageButtons(r, this->type, this->colour, clicked, this->widget_data.sprite, this->align);
3007 break;
3008
3009 case WWT_TEXTBTN:
3010 case WWT_PUSHTXTBTN:
3011 case WWT_TEXTBTN_2:
3012 DrawFrameRect(r, this->colour, clicked ? FrameFlag::Lowered : FrameFlags{});
3013 DrawLabel(r, this->text_colour, GetStringForWidget(w, this, (type & WWT_MASK) == WWT_TEXTBTN_2 && clicked), this->align, this->text_size);
3014 break;
3015
3016 case WWT_IMGTEXTBTN:
3017 case WWT_PUSHIMGTEXTBTN:
3018 DrawImageTextButtons(r, this->colour, clicked, this->widget_data.sprite, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3019 break;
3020
3021 case WWT_ARROWBTN:
3022 case WWT_PUSHARROWBTN: {
3023 SpriteID sprite;
3024 switch (this->widget_data.arrow_widget_type) {
3025 case AWV_DECREASE: sprite = _current_text_dir != TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
3026 case AWV_INCREASE: sprite = _current_text_dir == TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
3027 case AWV_LEFT: sprite = SPR_ARROW_LEFT; break;
3028 case AWV_RIGHT: sprite = SPR_ARROW_RIGHT; break;
3029 default: NOT_REACHED();
3030 }
3031 DrawImageButtons(r, WWT_PUSHIMGBTN, this->colour, clicked, sprite, this->align);
3032 break;
3033 }
3034
3035 case WWT_LABEL:
3036 DrawLabel(r, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3037 break;
3038
3039 case WWT_TEXT:
3040 DrawText(r, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3041 break;
3042
3043 case WWT_MATRIX:
3044 DrawMatrix(r, this->colour, clicked, this->widget_data.matrix.width, this->widget_data.matrix.height, this->resize_x, this->resize_y);
3045 break;
3046
3047 case WWT_EDITBOX: {
3048 const QueryString *query = w->GetQueryString(this->index);
3049 if (query != nullptr) query->DrawEditBox(w, this->index);
3050 break;
3051 }
3052
3053 case WWT_CAPTION:
3054 DrawCaption(r, this->colour, w->owner, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3055 break;
3056
3057 case WWT_SHADEBOX:
3058 DrawShadeBox(r, this->colour, w->IsShaded());
3059 break;
3060
3061 case WWT_DEBUGBOX:
3062 DrawDebugBox(r, this->colour, clicked);
3063 break;
3064
3065 case WWT_STICKYBOX:
3067 break;
3068
3069 case WWT_DEFSIZEBOX:
3070 DrawDefSizeBox(r, this->colour, clicked);
3071 break;
3072
3073 case WWT_RESIZEBOX:
3074 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);
3075 break;
3076
3077 case WWT_CLOSEBOX:
3078 DrawCloseBox(r, this->colour);
3079 break;
3080
3081 case WWT_DROPDOWN:
3082 DrawButtonDropdown(r, this->colour, false, clicked, GetStringForWidget(w, this), this->align);
3083 break;
3084
3086 case NWID_PUSHBUTTON_DROPDOWN:
3087 DrawButtonDropdown(r, this->colour, clicked, this->disp_flags.Test(NWidgetDisplayFlag::DropdownActive), GetStringForWidget(w, this), this->align);
3088 break;
3089
3090 default:
3091 NOT_REACHED();
3092 }
3093 if (this->index >= 0) w->DrawWidget(r, this->index);
3094
3095 if (this->IsDisabled() && this->type != WWT_BOOLBTN) {
3096 /* WWT_BOOLBTN is excluded as it draws its own disabled state. */
3098 }
3099
3100 DrawOutline(w, this);
3101}
3102
3111{
3112 if (_current_text_dir == TD_LTR) {
3113 int button_width = this->pos_x + this->current_x - NWidgetLeaf::dropdown_dimension.width;
3114 return pt.x < button_width;
3115 } else {
3116 int button_left = this->pos_x + NWidgetLeaf::dropdown_dimension.width;
3117 return pt.x >= button_left;
3118 }
3119}
3120
3121/* == Conversion code from NWidgetPart array to NWidgetBase* tree == */
3122
3129{
3130 return tp > WPT_ATTRIBUTE_BEGIN && tp < WPT_ATTRIBUTE_END;
3131}
3132
3140{
3141 switch (nwid.type) {
3142 case WPT_RESIZE: {
3143 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3144 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_RESIZE requires NWidgetResizeBase");
3145 assert(nwid.u.xy.x >= 0 && nwid.u.xy.y >= 0);
3146 nwrb->SetResize(nwid.u.xy.x, nwid.u.xy.y);
3147 break;
3148 }
3149
3150 case WPT_MINSIZE: {
3151 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3152 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_MINSIZE requires NWidgetResizeBase");
3153 assert(nwid.u.xy.x >= 0 && nwid.u.xy.y >= 0);
3154 nwrb->SetMinimalSize(nwid.u.xy.x, nwid.u.xy.y);
3155 break;
3156 }
3157
3158 case WPT_MINTEXTLINES: {
3159 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3160 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_MINTEXTLINES requires NWidgetResizeBase");
3161 assert(nwid.u.text_lines.size >= FS_BEGIN && nwid.u.text_lines.size < FS_END);
3163 break;
3164 }
3165
3166 case WPT_TEXTSTYLE: {
3167 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3168 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_TEXTSTYLE requires NWidgetCore");
3169 nwc->SetTextStyle(nwid.u.text_style.colour, nwid.u.text_style.size);
3170 break;
3171 }
3172
3173 case WPT_ALIGNMENT: {
3174 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3175 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_ALIGNMENT requires NWidgetCore");
3176 nwc->SetAlignment(nwid.u.align.align);
3177 break;
3178 }
3179
3180 case WPT_FILL: {
3181 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3182 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_FILL requires NWidgetResizeBase");
3183 nwrb->SetFill(nwid.u.xy.x, nwid.u.xy.y);
3184 break;
3185 }
3186
3187 case WPT_DATATIP: {
3188 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3189 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_DATATIP requires NWidgetCore");
3190 nwc->widget_data = nwid.u.data_tip.data;
3191 nwc->SetToolTip(nwid.u.data_tip.tooltip);
3192 break;
3193 }
3194
3195 case WPT_PADDING:
3196 if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_PADDING requires NWidgetBase");
3197 dest->SetPadding(nwid.u.padding);
3198 break;
3199
3200 case WPT_PIPSPACE: {
3201 NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(dest);
3202 if (nwc != nullptr) nwc->SetPIP(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3203
3204 NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(dest);
3205 if (nwb != nullptr) nwb->SetPIP(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3206
3207 if (nwc == nullptr && nwb == nullptr) [[unlikely]] throw std::runtime_error("WPT_PIPSPACE requires NWidgetPIPContainer or NWidgetBackground");
3208 break;
3209 }
3210
3211 case WPT_PIPRATIO: {
3212 NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(dest);
3213 if (nwc != nullptr) nwc->SetPIPRatio(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3214
3215 NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(dest);
3216 if (nwb != nullptr) nwb->SetPIPRatio(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3217
3218 if (nwc == nullptr && nwb == nullptr) [[unlikely]] throw std::runtime_error("WPT_PIPRATIO requires NWidgetPIPContainer or NWidgetBackground");
3219 break;
3220 }
3221
3222 case WPT_SCROLLBAR: {
3223 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3224 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_SCROLLBAR requires NWidgetCore");
3225 nwc->scrollbar_index = nwid.u.widget.index;
3226 break;
3227 }
3228
3229 case WPT_ASPECT: {
3230 if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_ASPECT requires NWidgetBase");
3231 dest->aspect_ratio = nwid.u.aspect.ratio;
3232 dest->aspect_flags = nwid.u.aspect.flags;
3233 break;
3234 }
3235
3236 default:
3237 NOT_REACHED();
3238 }
3239}
3240
3247static std::unique_ptr<NWidgetBase> MakeNWidget(const NWidgetPart &nwid)
3248{
3249 assert(!IsAttributeWidgetPartType(nwid.type));
3250 assert(nwid.type != WPT_ENDCONTAINER);
3251
3252 switch (nwid.type) {
3253 case NWID_SPACER: return std::make_unique<NWidgetSpacer>(0, 0);
3254
3255 case WWT_PANEL: [[fallthrough]];
3256 case WWT_INSET: [[fallthrough]];
3257 case WWT_FRAME: return std::make_unique<NWidgetBackground>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index);
3258
3259 case NWID_HORIZONTAL: return std::make_unique<NWidgetHorizontal>(nwid.u.container.flags, nwid.u.container.index);
3260 case NWID_HORIZONTAL_LTR: return std::make_unique<NWidgetHorizontalLTR>(nwid.u.container.flags, nwid.u.container.index);
3261 case NWID_VERTICAL: return std::make_unique<NWidgetVertical>(nwid.u.container.flags, nwid.u.container.index);
3262 case NWID_SELECTION: return std::make_unique<NWidgetStacked>(nwid.u.widget.index);
3263 case NWID_MATRIX: return std::make_unique<NWidgetMatrix>(nwid.u.widget.colour, nwid.u.widget.index);
3264 case NWID_VIEWPORT: return std::make_unique<NWidgetViewport>(nwid.u.widget.index);
3265 case NWID_LAYER: return std::make_unique<NWidgetLayer>(nwid.u.widget.index);
3266
3267 case NWID_HSCROLLBAR: [[fallthrough]];
3268 case NWID_VSCROLLBAR: return std::make_unique<NWidgetScrollbar>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index);
3269
3270 case WPT_FUNCTION: return nwid.u.func_ptr();
3271
3272 default:
3273 assert((nwid.type & WWT_MASK) < WWT_LAST || (nwid.type & WWT_MASK) == NWID_BUTTON_DROPDOWN);
3274 return std::make_unique<NWidgetLeaf>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index, WidgetData{}, STR_NULL);
3275 }
3276}
3277
3291static 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)
3292{
3293 dest = nullptr;
3294
3295 if (IsAttributeWidgetPartType(nwid_begin->type)) [[unlikely]] throw std::runtime_error("Expected non-attribute NWidgetPart type");
3296 if (nwid_begin->type == WPT_ENDCONTAINER) return nwid_begin;
3297
3298 fill_dest = IsContainerWidgetType(nwid_begin->type);
3299 dest = MakeNWidget(*nwid_begin);
3300 if (dest == nullptr) return nwid_begin;
3301
3302 ++nwid_begin;
3303
3304 /* Once a widget is created, we're now looking for attributes. */
3305 while (nwid_begin != nwid_end && IsAttributeWidgetPartType(nwid_begin->type)) {
3306 ApplyNWidgetPartAttribute(*nwid_begin, dest.get());
3307 ++nwid_begin;
3308 }
3309
3310 return nwid_begin;
3311}
3312
3319{
3320 return tp == NWID_HORIZONTAL || tp == NWID_HORIZONTAL_LTR || tp == NWID_VERTICAL || tp == NWID_MATRIX
3321 || tp == WWT_PANEL || tp == WWT_FRAME || tp == WWT_INSET || tp == NWID_SELECTION || tp == NWID_LAYER;
3322}
3323
3331static 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)
3332{
3333 /* If *parent == nullptr, only the first widget is read and returned. Otherwise, *parent must point to either
3334 * a #NWidgetContainer or a #NWidgetBackground object, and parts are added as much as possible. */
3335 NWidgetContainer *nwid_cont = dynamic_cast<NWidgetContainer *>(parent.get());
3336 NWidgetBackground *nwid_parent = dynamic_cast<NWidgetBackground *>(parent.get());
3337 assert(parent == nullptr || (nwid_cont != nullptr && nwid_parent == nullptr) || (nwid_cont == nullptr && nwid_parent != nullptr));
3338
3339 while (nwid_begin != nwid_end) {
3340 std::unique_ptr<NWidgetBase> sub_widget = nullptr;
3341 bool fill_sub = false;
3342 nwid_begin = MakeNWidget(nwid_begin, nwid_end, sub_widget, fill_sub);
3343
3344 /* Break out of loop when end reached */
3345 if (sub_widget == nullptr) break;
3346
3347 /* If sub-widget is a container, recursively fill that container. */
3348 if (fill_sub && IsContainerWidgetType(sub_widget->type)) {
3349 nwid_begin = MakeWidgetTree(nwid_begin, nwid_end, sub_widget);
3350 }
3351
3352 /* Add sub_widget to parent container if available, otherwise return the widget to the caller. */
3353 if (nwid_cont != nullptr) nwid_cont->Add(std::move(sub_widget));
3354 if (nwid_parent != nullptr) nwid_parent->Add(std::move(sub_widget));
3355 if (nwid_cont == nullptr && nwid_parent == nullptr) {
3356 parent = std::move(sub_widget);
3357 return nwid_begin;
3358 }
3359 }
3360
3361 if (nwid_begin == nwid_end) return nwid_begin; // Reached the end of the array of parts?
3362
3363 assert(nwid_begin < nwid_end);
3364 assert(nwid_begin->type == WPT_ENDCONTAINER);
3365 return std::next(nwid_begin); // *nwid_begin is also 'used'
3366}
3367
3375std::unique_ptr<NWidgetBase> MakeNWidgets(std::span<const NWidgetPart> nwid_parts, std::unique_ptr<NWidgetBase> &&container)
3376{
3377 if (container == nullptr) container = std::make_unique<NWidgetVertical>();
3378 [[maybe_unused]] auto nwid_part = MakeWidgetTree(std::begin(nwid_parts), std::end(nwid_parts), container);
3379#ifdef WITH_ASSERT
3380 if (nwid_part != std::end(nwid_parts)) [[unlikely]] throw std::runtime_error("Did not consume all NWidgetParts");
3381#endif
3382 return std::move(container);
3383}
3384
3394std::unique_ptr<NWidgetBase> MakeWindowNWidgetTree(std::span<const NWidgetPart> nwid_parts, NWidgetStacked **shade_select)
3395{
3396 auto nwid_begin = std::begin(nwid_parts);
3397 auto nwid_end = std::end(nwid_parts);
3398
3399 *shade_select = nullptr;
3400
3401 /* Read the first widget recursively from the array. */
3402 std::unique_ptr<NWidgetBase> nwid = nullptr;
3403 nwid_begin = MakeWidgetTree(nwid_begin, nwid_end, nwid);
3404 assert(nwid != nullptr);
3405
3406 NWidgetHorizontal *hor_cont = dynamic_cast<NWidgetHorizontal *>(nwid.get());
3407
3408 auto root = std::make_unique<NWidgetVertical>();
3409 root->Add(std::move(nwid));
3410 if (nwid_begin == nwid_end) return root; // There is no body at all.
3411
3412 if (hor_cont != nullptr && hor_cont->GetWidgetOfType(WWT_CAPTION) != nullptr && hor_cont->GetWidgetOfType(WWT_SHADEBOX) != nullptr) {
3413 /* If the first widget has a title bar and a shade box, silently add a shade selection widget in the tree. */
3414 auto shade_stack = std::make_unique<NWidgetStacked>(INVALID_WIDGET);
3415 *shade_select = shade_stack.get();
3416 /* Load the remaining parts into the shade stack. */
3417 shade_stack->Add(MakeNWidgets({nwid_begin, nwid_end}, std::make_unique<NWidgetVertical>()));
3418 root->Add(std::move(shade_stack));
3419 return root;
3420 }
3421
3422 /* Load the remaining parts into 'root'. */
3423 return MakeNWidgets({nwid_begin, nwid_end}, std::move(root));
3424}
3425
3436std::unique_ptr<NWidgetBase> MakeCompanyButtonRows(WidgetID widget_first, WidgetID widget_last, Colours button_colour, int max_length, StringID button_tooltip, bool resizable)
3437{
3438 assert(max_length >= 1);
3439 std::unique_ptr<NWidgetVertical> vert = nullptr; // Storage for all rows.
3440 std::unique_ptr<NWidgetHorizontal> hor = nullptr; // Storage for buttons in one row.
3441 int hor_length = 0;
3442
3443 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON, nullptr, ZoomLevel::Normal);
3444 sprite_size.width += WidgetDimensions::unscaled.matrix.Horizontal();
3445 sprite_size.height += WidgetDimensions::unscaled.matrix.Vertical();
3446
3447 for (WidgetID widnum = widget_first; widnum <= widget_last; widnum++) {
3448 /* Ensure there is room in 'hor' for another button. */
3449 if (hor_length == max_length) {
3450 if (vert == nullptr) vert = std::make_unique<NWidgetVertical>();
3451 vert->Add(std::move(hor));
3452 hor = nullptr;
3453 hor_length = 0;
3454 }
3455 if (hor == nullptr) {
3456 hor = std::make_unique<NWidgetHorizontal>();
3457 hor_length = 0;
3458 }
3459
3460 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, button_colour, widnum);
3461 panel->SetMinimalSize(sprite_size.width, sprite_size.height);
3462 panel->SetFill(1, 1);
3463 if (resizable) panel->SetResize(1, 0);
3464 panel->SetToolTip(button_tooltip);
3465 hor->Add(std::move(panel));
3466 hor_length++;
3467 }
3468 if (vert == nullptr) return hor; // All buttons fit in a single row.
3469
3470 if (hor_length > 0 && hor_length < max_length) {
3471 /* Last row is partial, add a spacer at the end to force all buttons to the left. */
3472 auto spc = std::make_unique<NWidgetSpacer>(sprite_size.width, sprite_size.height);
3473 spc->SetFill(1, 1);
3474 if (resizable) spc->SetResize(1, 0);
3475 hor->Add(std::move(spc));
3476 }
3477 if (hor != nullptr) vert->Add(std::move(hor));
3478 return vert;
3479}
3480
3487{
3488 assert(parent_window != nullptr);
3489 if (parent_window->nested_focus != nullptr) {
3490 for (auto &widget : this->children) {
3491 if (parent_window->nested_focus == widget.get()) {
3492 parent_window->UnfocusFocusedWidget();
3493 }
3494 }
3495 }
3496}
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.
Nested widget with a child.
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:2338
NWidgetBase * GetWidgetOfType(WidgetType tp) override
Retrieve a widget by its type.
Definition widget.cpp:2348
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:2284
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2215
void AdjustPaddingForZoom() override
Adjust the padding based on the user interface zoom.
Definition widget.cpp:2209
NWidgetBackground(WidgetType tp, Colours colour, WidgetID index, std::unique_ptr< NWidgetPIPContainer > &&child=nullptr)
Constructor parent nested widgets.
Definition widget.cpp:2147
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:2181
std::unique_ptr< NWidgetPIPContainer > child
Child widget.
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2302
void Add(std::unique_ptr< NWidgetBase > &&nwid)
Add a child to the parent.
Definition widget.cpp:2162
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:2296
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:2200
Baseclass for nested widgets.
void StoreSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height)
Store size and position.
virtual TextColour GetHighlightColour() const
Get the colour of the highlighted text.
float aspect_ratio
Desired aspect ratio of widget.
virtual bool IsHighlighted() const
Whether the widget is currently highlighted or not.
virtual void AdjustPaddingForZoom()
Adjust the padding based on the user interface zoom.
Definition widget.cpp:970
virtual void SetDirty(const Window *w) const
Mark the widget as 'dirty' (in need of repaint).
Definition widget.cpp:931
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.
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.
virtual void Draw(const Window *w)=0
Draw the widgets of the tree.
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 (INVALID_WIDGET means 'not used').
virtual NWidgetBase * GetWidgetOfType(WidgetType tp)
Retrieve a widget by its type.
Definition widget.cpp:951
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:915
Baseclass for container widgets.
void Add(std::unique_ptr< NWidgetBase > &&wid)
Append widget wid to container.
Definition widget.cpp:1293
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1300
void UnfocusWidgets(Window *parent_window)
Unfocuses the focused widget of the window, if the focused widget is contained inside the container.
Definition widget.cpp:3486
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1308
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:1317
void AdjustPaddingForZoom() override
Adjust the padding based on the user interface zoom.
Definition widget.cpp:1281
NWidgetBase * GetWidgetOfType(WidgetType tp) override
Retrieve a widget by its type.
Definition widget.cpp:1271
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:1225
bool IsDisabled() const
Return whether the widget is disabled.
void SetSprite(SpriteID sprite)
Set sprite of the nested widget.
Definition widget.cpp:1175
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:1215
WidgetID GetScrollbarIndex() const
Get the WidgetID of this nested widget's scrollbar.
Definition widget.cpp:1261
void SetAlignment(StringAlignment align)
Set the text/image alignment of the nested widget.
Definition widget.cpp:1243
void SetResizeWidgetType(ResizeWidgetValues type)
Set the resize widget type of the nested widget.
Definition widget.cpp:1205
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:1143
void SetSpriteTip(SpriteID sprite, StringID tool_tip)
Set sprite and tool tip of the nested widget.
Definition widget.cpp:1185
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:1252
WidgetID scrollbar_index
Index of an attached scrollbar.
StringID GetToolTip() const
Get the tool tip of the nested widget.
Definition widget.cpp:1234
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1266
void SetString(StringID string)
Set string of the nested widget.
Definition widget.cpp:1155
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:1196
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:1165
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:1714
Horizontal container.
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1536
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:1605
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:1464
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1442
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1482
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2966
static void InvalidateDimensionCache()
Reset the cached dimensions.
Definition widget.cpp:2643
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:2670
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:2779
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:3110
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2061
int count
Amount of valid elements.
void SetClicked(int clicked)
Sets the clicked element in the matrix.
Definition widget.cpp:1930
int GetCurrentElement() const
Get current element.
Definition widget.cpp:1980
Scrollbar * sb
The scrollbar we're associated with.
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1985
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:1971
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:2116
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:2007
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:2031
int clicked
The currently clicked element.
void SetCount(int count)
Set the number of elements in this matrix.
Definition widget.cpp:1947
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.
void SetPIPRatio(uint8_t pip_ratio_pre, uint8_t pip_ratio_inter, uint8_t pip_ratio_post)
Set additional pre/inter/post space for the container.
Definition widget.cpp:1529
uint8_t uz_pip_inter
Unscaled space between widgets.
uint8_t pip_ratio_pre
Ratio of remaining space before first widget.
void AdjustPaddingForZoom() override
Adjust the padding based on the user interface zoom.
Definition widget.cpp:1492
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:1509
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:1024
bool UpdateSize(uint min_x, uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1107
void AdjustPaddingForZoom() override
Adjust the padding based on the user interface zoom.
Definition widget.cpp:1010
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:982
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:1063
void SetMinimalTextLines(uint8_t min_lines, uint8_t spacing, FontSize size)
Set minimal text lines for the widget.
Definition widget.cpp:1050
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:1087
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:1128
bool UpdateVerticalSize(uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1121
void SetResize(uint resize_x, uint resize_y)
Set resize step of the widget.
Definition widget.cpp:1074
void SetAspect(float ratio, AspectFlags flags=AspectFlag::ResizeX)
Set desired aspect ratio of this widget.
Definition widget.cpp:993
void SetMinimalSizeAbsolute(uint min_x, uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1037
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:2587
NWidgetScrollbar(WidgetType tp, Colours colour, WidgetID index)
Scrollbar widget.
Definition widget.cpp:2545
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2566
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:1891
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1921
void SetDirty(const Window *w) const override
Mark the widget as 'dirty' (in need of repaint).
Definition widget.cpp:1916
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1904
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1897
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:1328
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:1369
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1399
bool SetDisplayedPlane(int plane)
Select which plane to show (for NWID_SELECTION only).
Definition widget.cpp:1423
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1389
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1408
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:1788
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1719
void UpdateViewportCoordinates(Window *w)
Update the position and size of the viewport (after eg a resize).
Definition widget.cpp:2403
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2367
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2360
void InitializeViewport(Window *w, std::variant< TileIndex, VehicleID > focus, ZoomLevel zoom)
Initialize the viewport of the window.
Definition widget.cpp:2394
Scrollbar data structure.
size_type GetCapacity() const
Gets the number of visible elements of the scrollbar.
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:2426
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:2500
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:2447
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
static constexpr uint WD_DROPDOWN_HEIGHT
Minimum height of a drop down widget.
Definition window_gui.h:88
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:30
RectPadding hscrollbar
Padding inside horizontal scrollbar buttons.
Definition window_gui.h:37
RectPadding vscrollbar
Padding inside vertical scrollbar buttons.
Definition window_gui.h:36
static constexpr uint WD_CLOSEBOX_WIDTH
Minimum width of a close box widget.
Definition window_gui.h:86
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
static constexpr uint WD_DEBUGBOX_WIDTH
Minimum width of a standard debug box widget.
Definition window_gui.h:83
static constexpr uint WD_DEFSIZEBOX_WIDTH
Minimum width of a standard defsize box widget.
Definition window_gui.h:84
static constexpr uint WD_SHADEBOX_WIDTH
Distances used in drawing widgets.
Definition window_gui.h:81
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:87
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:717
Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
Get the size of a sprite.
Definition gfx.cpp:972
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:900
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:669
void DrawRectOutline(const Rect &r, PixelColour colour, int width, int dash)
Draw the outline of a Rect.
Definition gfx.cpp:464
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:1038
void GfxFillRect(int left, int top, int right, int bottom, const std::variant< PixelColour, PaletteID > &colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition gfx.cpp:116
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:1573
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:248
@ FS_BEGIN
First font.
Definition gfx_type.h:255
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:249
StringAlignment
How to align the to-be drawn text.
Definition gfx_type.h:387
@ SA_TOP
Top align the text.
Definition gfx_type.h:393
@ SA_LEFT
Left align the text.
Definition gfx_type.h:388
@ SA_HOR_MASK
Mask for horizontal alignment.
Definition gfx_type.h:391
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:390
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:389
@ SA_VERT_MASK
Mask for vertical alignment.
Definition gfx_type.h:396
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition gfx_type.h:400
@ SA_BOTTOM
Bottom align the text.
Definition gfx_type.h:395
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:398
@ SA_VERT_CENTER
Vertically center the text.
Definition gfx_type.h:394
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:307
@ FILLRECT_CHECKER
Draw only every second pixel, used for greying-out.
Definition gfx_type.h:346
@ FILLRECT_RECOLOUR
Apply a recolour sprite to the screen content.
Definition gfx_type.h:347
constexpr NWidgetPart SetFill(uint16_t fill_x, uint16_t fill_y)
Widget part function for setting filling.
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:3394
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:3375
constexpr NWidgetPart SetToolTip(StringID tip)
Widget part function for setting tooltip and clearing the widget data.
constexpr NWidgetPart SetAlignment(StringAlignment align)
Widget part function for setting the alignment of text/images.
constexpr NWidgetPart SetResize(int16_t dx, int16_t dy)
Widget part function for setting the resize step.
void SetDirty() const
Mark entire window as dirty (in need of re-paint).
Definition window.cpp:969
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:1521
#define Rect
Macro that prevents name conflicts between included headers.
#define Point
Macro that prevents name conflicts between included headers.
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
PixelColour GetColourGradient(Colours colour, ColourShade shade)
Get colour gradient palette index.
Definition palette.cpp:388
static constexpr PixelColour PC_BLACK
Black palette colour.
static constexpr PixelColour PC_WHITE
White 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:1546
static const PaletteID PALETTE_TO_TRANSPARENT
This sets the sprite to transparent.
Definition sprites.h:1616
static const PaletteID PALETTE_NEWSPAPER
Recolour sprite for newspaper-greying.
Definition sprites.h:1618
Definition of base types and functions in a cross-platform compatible way.
The colour translation of GRF's strings.
static constexpr PixelColour _string_colourmap[17]
Colour mapping for TextColour.
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:424
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:56
Functions related to OTTD's strings.
Types related to strings.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
@ TD_LTR
Text is written left-to-right by default.
@ TD_RTL
Text is written right-to-left by default.
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
T y
Y coordinate.
T x
X coordinate.
Dimensions (a width and height) of a rectangle in 2D.
Data about how and where to blit pixels.
Definition gfx_type.h:157
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.
Colour for pixel/line drawing.
Definition gfx_type.h:405
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.
Rect CentreToHeight(int height) const
Centre a vertical dimension within this Rect.
int Height() const
Get height of Rect.
Rect WithY(int new_top, int new_bottom) const
Create a new Rect, replacing the top and bottom coordiates.
Rect WithX(int new_left, int new_right) const
Create a new Rect, replacing the left and right coordiates.
Rect Expand(int s) const
Copy and expand Rect by s pixels.
Data structure for viewport, display of a part of the world.
int top
Screen coordinate top edge of the viewport.
int width
Screen width of the viewport.
ZoomLevel zoom
The zoom level of the viewport.
int virtual_width
width << zoom
int left
Screen coordinate left edge of the viewport.
int height
Screen height of the viewport.
int virtual_height
height << zoom
Container with the data associated to a single widget.
Data structure for an opened window.
Definition window_gui.h:274
virtual void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize)
Update size and resize step of a widget in the window.
Definition window_gui.h:621
static int SortButtonWidth()
Get width of up/down arrow of sort button state.
Definition widget.cpp:815
void DrawWidgets() const
Paint all widgets of a window.
Definition widget.cpp:766
std::unique_ptr< ViewportData > viewport
Pointer to viewport data, if present.
Definition window_gui.h:319
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:507
WidgetID mouse_capture_widget
ID of current mouse capture widget (e.g. dragged scrollbar). INVALID_WIDGET if no widget has mouse ca...
Definition window_gui.h:327
virtual void OnScrollbarScroll(WidgetID widget)
Notify window that a scrollbar position has been updated.
Definition window_gui.h:721
void DrawSortButtonState(WidgetID widget, SortButtonState state) const
Draw a sort button's up or down arrow symbol.
Definition widget.cpp:798
void UnfocusFocusedWidget()
Makes no widget on this window have focus.
Definition window.cpp:472
virtual bool IsNewGRFInspectable() const
Is the data related to this window NewGRF inspectable?
Definition window_gui.h:863
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:607
Owner owner
The owner of the content shown in this window. Company colour is acquired from this variable.
Definition window_gui.h:317
int left
x position of left edge of the window
Definition window_gui.h:310
bool IsShaded() const
Is window shaded currently?
Definition window_gui.h:560
const NWidgetCore * nested_focus
Currently focused nested widget, or nullptr if no nested widget has focus.
Definition window_gui.h:320
int top
y position of top edge of the window
Definition window_gui.h:311
const QueryString * GetQueryString(WidgetID widnum) const
Return the querystring associated to a editbox.
Definition window.cpp:336
WidgetLookup widget_lookup
Indexed access to the nested widget tree. Do not access directly, use Window::GetWidget() instead.
Definition window_gui.h:323
int GetRowFromWidget(int clickpos, WidgetID widget, int padding, int line_height=-1) const
Compute the row of a widget that a user clicked in.
Definition window.cpp:212
const NWID * GetWidget(WidgetID widnum) const
Get the nested widget with number widnum from the nested widget tree.
Definition window_gui.h:986
WindowFlags flags
Window flags.
Definition window_gui.h:301
std::unique_ptr< NWidgetBase > nested_root
Root of the nested tree.
Definition window_gui.h:322
int height
Height of the window (number of pixels down in y direction).
Definition window_gui.h:313
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:312
Functions related to transparency.
TransparencyOptionBits _transparency_opt
The bits that should be transparent.
uint TransparencyOptionBits
transparency option bits
@ TO_TEXT
loading and cost/income text
@ TO_SIGNS
signs
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:697
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:289
Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
Apply 'scroll' to a rect to be drawn in.
Definition widget.cpp:2517
void ApplyNWidgetPartAttribute(const NWidgetPart &nwid, NWidgetBase *dest)
Apply an attribute NWidgetPart to an NWidget.
Definition widget.cpp:3139
static std::pair< int, int > HandleScrollbarHittest(const Scrollbar *sb, int mi, int ma, bool horizontal)
Compute the vertical position of the draggable part of scrollbar.
Definition widget.cpp:149
static void DrawDefSizeBox(const Rect &r, Colours colour, bool clicked)
Draw a defsize box.
Definition widget.cpp:658
bool IsContainerWidgetType(WidgetType tp)
Test if WidgetType is a container widget.
Definition widget.cpp:3318
static void DrawDebugBox(const Rect &r, Colours colour, bool clicked)
Draw a NewGRF debug box.
Definition widget.cpp:669
static void DrawStickyBox(const Rect &r, Colours colour, bool clicked)
Draw a sticky box.
Definition widget.cpp:647
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:583
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:364
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:443
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:542
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:748
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:3247
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:718
static void DrawShadeBox(const Rect &r, Colours colour, bool clicked)
Draw a shade box.
Definition widget.cpp:636
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:274
static void DrawImageButtons(const Rect &r, WidgetType type, Colours colour, bool clicked, SpriteID img, StringAlignment align)
Draw an image button.
Definition widget.cpp:344
static bool IsAttributeWidgetPartType(WidgetType tp)
Test if (an NWidgetPart) WidgetType is an attribute widget part type.
Definition widget.cpp:3128
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:392
void ScrollbarClickHandler(Window *w, NWidgetCore *nw, int x, int y)
Special handling for the scrollbar widget type.
Definition widget.cpp:250
static void DrawResizeBox(const Rect &r, Colours colour, bool at_left, bool clicked, bool bevel)
Draw a resize box.
Definition widget.cpp:682
static void DrawText(const Rect &r, TextColour colour, std::string_view str, StringAlignment align, FontSize fs)
Draw text.
Definition widget.cpp:409
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:185
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:427
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:3331
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:501
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:3436
WidgetType
Window widget types, nested widget types, and nested widget part types.
Definition widget_type.h:35
@ 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:40
@ WPT_FILL
Widget part for specifying fill.
Definition widget_type.h:84
@ 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:90
@ WWT_IMGBTN
(Toggle) Button with image
Definition widget_type.h:41
@ 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:42
@ WWT_PUSHIMGBTN
Normal push-button (no toggle button) with image caption.
@ WPT_MINSIZE
Widget part for specifying minimal size.
Definition widget_type.h:82
@ WWT_PUSHARROWBTN
Normal push-button (no toggle button) with arrow caption.
@ WWT_LABEL
Centered label.
Definition widget_type.h:48
@ NWID_BUTTON_DROPDOWN
Button with a drop-down.
Definition widget_type.h:74
@ NWID_SPACER
Invisible widget that takes some space.
Definition widget_type.h:70
@ WWT_EDITBOX
a textbox for typing
Definition widget_type.h:62
@ WWT_ARROWBTN
(Toggle) Button with an arrow
Definition widget_type.h:43
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:66
@ WWT_TEXTBTN
(Toggle) Button with text
Definition widget_type.h:44
@ WPT_SCROLLBAR
Widget part for attaching a scrollbar.
Definition widget_type.h:91
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:39
@ WPT_ASPECT
Widget part for specifying aspect ratio.
Definition widget_type.h:92
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX).
Definition widget_type.h:57
@ WPT_RESIZE
Widget part for specifying resizing.
Definition widget_type.h:81
@ WWT_IMGTEXTBTN
(Toggle) Button with image and text
Definition widget_type.h:47
@ WWT_MATRIX
Grid of rows and columns.
Definition widget_type.h:50
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX).
Definition widget_type.h:55
@ WWT_CAPTION
Window caption (window title between closebox and stickybox).
Definition widget_type.h:52
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:76
@ WPT_TEXTSTYLE
Widget part for specifying text colour.
Definition widget_type.h:89
@ WPT_PADDING
Widget part for specifying a padding.
Definition widget_type.h:86
@ WWT_BOOLBTN
Standard boolean toggle button.
Definition widget_type.h:46
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:68
@ WWT_CLOSEBOX
Close box (at top-left of a window).
Definition widget_type.h:60
@ WWT_TEXTBTN_2
(Toggle) Button with diff text when clicked
Definition widget_type.h:45
@ WWT_FRAME
Frame.
Definition widget_type.h:51
@ WPT_MINTEXTLINES
Widget part for specifying minimal number of lines of text.
Definition widget_type.h:83
@ WWT_EMPTY
Empty widget, place holder to reserve space in widget tree.
Definition widget_type.h:37
@ WWT_LAST
Last Item. use WIDGETS_END to fill up padding!!
Definition widget_type.h:63
@ WPT_ATTRIBUTE_END
End marker for attribute NWidgetPart types.
Definition widget_type.h:93
@ NWID_HSCROLLBAR
Horizontal scrollbar.
Definition widget_type.h:75
@ WPT_ENDCONTAINER
Widget part to denote end of a container.
Definition widget_type.h:96
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window).
Definition widget_type.h:59
@ WPT_PIPRATIO
Widget part for specifying pre/inter/post ratio for containers.
Definition widget_type.h:88
@ WPT_DATATIP
Widget part for specifying data and tooltip.
Definition widget_type.h:85
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX).
Definition widget_type.h:56
@ WPT_ATTRIBUTE_BEGIN
Begin marker for attribute NWidgetPart types.
Definition widget_type.h:80
@ WPT_PIPSPACE
Widget part for specifying pre/inter/post space for containers.
Definition widget_type.h:87
@ NWID_MATRIX
Matrix container.
Definition widget_type.h:69
@ NWID_VIEWPORT
Nested widget containing a viewport.
Definition widget_type.h:73
@ WWT_DROPDOWN
Drop down list.
Definition widget_type.h:61
@ WWT_TEXT
Pure simple text.
Definition widget_type.h:49
@ NWID_HORIZONTAL_LTR
Horizontal container that doesn't change the order of the widgets for RTL languages.
Definition widget_type.h:67
@ WPT_FUNCTION
Widget part for calling a user function.
Definition widget_type.h:95
@ WWT_DEBUGBOX
NewGRF debug box (at top-right of a window, between WWT_CAPTION and WWT_SHADEBOX).
Definition widget_type.h:54
@ NWID_LAYER
Layered widgets, all visible together.
Definition widget_type.h:72
@ NWID_SELECTION
Stacked widgets, only one visible at a time (eg in a panel with tabs).
Definition widget_type.h:71
@ 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:22
@ AWV_RIGHT
Force the arrow to the right.
Definition widget_type.h:23
@ AWV_DECREASE
Arrow to the left or in case of RTL to the right.
Definition widget_type.h:20
@ AWV_INCREASE
Arrow to the right or in case of RTL to the left.
Definition widget_type.h:21
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:27
@ RWV_SHOW_BEVEL
Bevel of resize box is shown.
Definition widget_type.h:28
bool _window_highlight_colour
If false, highlight is white, otherwise the by the widget defined colour.
Definition window.cpp:78
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:289
@ Transparent
Makes the background transparent if set.
Definition window_gui.h:25
@ BorderOnly
Draw border only, no background.
Definition window_gui.h:26
@ Darkened
If set the background is darker, allows for lowered frames with normal background colour when used wi...
Definition window_gui.h:28
@ Lowered
If set the frame is lowered and the background colour brighter (ie. buttons when pressed).
Definition window_gui.h:27
@ SizingLeft
Window is being resized towards the left.
Definition window_gui.h:231
@ Highlighted
Window has a widget that has a highlight.
Definition window_gui.h:236
@ SizingRight
Window is being resized towards the right.
Definition window_gui.h:230
@ WhiteBorder
Window white border counter bit mask.
Definition window_gui.h:235
@ Sticky
Window is made sticky by user.
Definition window_gui.h:233
SortButtonState
State of a sort direction button.
Definition window_gui.h:217
@ SBS_DOWN
Sort ascending.
Definition window_gui.h:219
@ SBS_OFF
Do not sort (with this button).
Definition window_gui.h:218
int WidgetID
Widget ID.
Definition window_type.h:20
EventState
State of handling an event.
@ ES_HANDLED
The passed event is handled.
@ ES_NOT_HANDLED
The passed event is not handled.
static constexpr WidgetID INVALID_WIDGET
An invalid widget index.
Definition window_type.h:23
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:20
@ Normal
The normal zoom level.
Definition zoom_type.h:26