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