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