OpenTTD Source 20250205-master-gfd85ab1e2c
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 "window_gui.h"
14#include "viewport_func.h"
15#include "zoom_func.h"
16#include "strings_func.h"
17#include "transparency.h"
19#include "settings_type.h"
20#include "querystring_gui.h"
21
22#include "table/sprites.h"
23#include "table/strings.h"
25
26#include "safeguards.h"
27
29
35static inline RectPadding ScaleGUITrad(const RectPadding &r)
36{
37 return {(uint8_t)ScaleGUITrad(r.left), (uint8_t)ScaleGUITrad(r.top), (uint8_t)ScaleGUITrad(r.right), (uint8_t)ScaleGUITrad(r.bottom)};
38}
39
45static inline Dimension ScaleGUITrad(const Dimension &dim)
46{
47 return {(uint)ScaleGUITrad(dim.width), (uint)ScaleGUITrad(dim.height)};
48}
49
55{
56 Point offset;
57 Dimension d = GetSpriteSize(sprid, &offset, ZOOM_LVL_NORMAL);
58 d.width -= offset.x;
59 d.height -= offset.y;
60 return ScaleGUITrad(d);
61}
62
67{
74 } else {
76 }
91
97}
98
106static inline Point GetAlignedPosition(const Rect &r, const Dimension &d, StringAlignment align)
107{
108 Point p;
109 /* In case we have a RTL language we swap the alignment. */
110 if (!(align & SA_FORCE) && _current_text_dir == TD_RTL && (align & SA_HOR_MASK) != SA_HOR_CENTER) align ^= SA_RIGHT;
111 switch (align & SA_HOR_MASK) {
112 case SA_LEFT: p.x = r.left; break;
113 case SA_HOR_CENTER: p.x = CenterBounds(r.left, r.right, d.width); break;
114 case SA_RIGHT: p.x = r.right + 1 - d.width; break;
115 default: NOT_REACHED();
116 }
117 switch (align & SA_VERT_MASK) {
118 case SA_TOP: p.y = r.top; break;
119 case SA_VERT_CENTER: p.y = CenterBounds(r.top, r.bottom, d.height); break;
120 case SA_BOTTOM: p.y = r.bottom + 1 - d.height; break;
121 default: NOT_REACHED();
122 }
123 return p;
124}
125
135static Point HandleScrollbarHittest(const Scrollbar *sb, int top, int bottom, bool horizontal)
136{
137 /* Base for reversion */
138 int rev_base = top + bottom;
139 int button_size;
140 if (horizontal) {
141 button_size = NWidgetScrollbar::GetHorizontalDimension().width;
142 } else {
143 button_size = NWidgetScrollbar::GetVerticalDimension().height;
144 }
145 top += button_size; // top points to just below the up-button
146 bottom -= button_size; // bottom points to top of the down-button
147
148 int count = sb->GetCount();
149 int cap = sb->GetCapacity();
150
151 if (count > cap) {
152 int height = (bottom - top);
153 int slider_height = std::max(button_size, cap * height / count);
154 height -= slider_height;
155
156 top += height * sb->GetPosition() / (count - cap);
157 bottom = top + slider_height;
158 }
159
160 Point pt;
161 if (horizontal && _current_text_dir == TD_RTL) {
162 pt.x = rev_base - bottom;
163 pt.y = rev_base - top;
164 } else {
165 pt.x = top;
166 pt.y = bottom;
167 }
168 return pt;
169}
170
180static void ScrollbarClickPositioning(Window *w, NWidgetScrollbar *sb, int x, int y, int mi, int ma)
181{
182 int pos;
183 int button_size;
184 bool rtl = false;
185 bool changed = false;
186
187 if (sb->type == NWID_HSCROLLBAR) {
188 pos = x;
189 rtl = _current_text_dir == TD_RTL;
190 button_size = NWidgetScrollbar::GetHorizontalDimension().width;
191 } else {
192 pos = y;
193 button_size = NWidgetScrollbar::GetVerticalDimension().height;
194 }
195 if (pos < mi + button_size) {
196 /* Pressing the upper button? */
198 if (_scroller_click_timeout <= 1) {
199 _scroller_click_timeout = 3;
200 changed = sb->UpdatePosition(rtl ? 1 : -1);
201 }
203 } else if (pos >= ma - button_size) {
204 /* Pressing the lower button? */
206
207 if (_scroller_click_timeout <= 1) {
208 _scroller_click_timeout = 3;
209 changed = sb->UpdatePosition(rtl ? -1 : 1);
210 }
212 } else {
213 Point pt = HandleScrollbarHittest(sb, mi, ma, sb->type == NWID_HSCROLLBAR);
214
215 if (pos < pt.x) {
216 changed = sb->UpdatePosition(rtl ? 1 : -1, Scrollbar::SS_BIG);
217 } else if (pos > pt.y) {
218 changed = sb->UpdatePosition(rtl ? -1 : 1, Scrollbar::SS_BIG);
219 } else {
220 _scrollbar_start_pos = pt.x - mi - button_size;
221 _scrollbar_size = ma - mi - button_size * 2 - (pt.y - pt.x);
223 _cursorpos_drag_start = _cursor.pos;
224 }
225 }
226
227 if (changed) {
228 /* Position changed so refresh the window */
229 w->SetDirty();
230 } else {
231 /* No change so only refresh this scrollbar */
232 sb->SetDirty(w);
233 }
234}
235
244void ScrollbarClickHandler(Window *w, NWidgetCore *nw, int x, int y)
245{
246 int mi, ma;
247
248 if (nw->type == NWID_HSCROLLBAR) {
249 mi = nw->pos_x;
250 ma = nw->pos_x + nw->current_x;
251 } else {
252 mi = nw->pos_y;
253 ma = nw->pos_y + nw->current_y;
254 }
255 NWidgetScrollbar *scrollbar = dynamic_cast<NWidgetScrollbar*>(nw);
256 assert(scrollbar != nullptr);
257 ScrollbarClickPositioning(w, scrollbar, x, y, mi, ma);
258}
259
268WidgetID GetWidgetFromPos(const Window *w, int x, int y)
269{
270 NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
271 return (nw != nullptr) ? nw->GetIndex() : -1;
272}
273
283void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
284{
285 if (flags.Test(FrameFlag::Transparent)) {
286 GfxFillRect(left, top, right, bottom, PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOUR);
287 } else {
288 assert(colour < COLOUR_END);
289
290 const uint dark = GetColourGradient(colour, SHADE_DARK);
291 const uint medium_dark = GetColourGradient(colour, SHADE_LIGHT);
292 const uint medium_light = GetColourGradient(colour, SHADE_LIGHTER);
293 const uint light = GetColourGradient(colour, SHADE_LIGHTEST);
294 uint interior;
295
296 Rect outer = {left, top, right, bottom}; // Outside rectangle
297 Rect inner = outer.Shrink(WidgetDimensions::scaled.bevel); // Inside rectangle
298
299 if (flags.Test(FrameFlag::Lowered)) {
300 GfxFillRect(outer.left, outer.top, inner.left - 1, outer.bottom, dark); // Left
301 GfxFillRect(inner.left, outer.top, outer.right, inner.top - 1, dark); // Top
302 GfxFillRect(inner.right + 1, inner.top, outer.right, inner.bottom, light); // Right
303 GfxFillRect(inner.left, inner.bottom + 1, outer.right, outer.bottom, light); // Bottom
304 interior = (flags.Test(FrameFlag::Darkened) ? medium_dark : medium_light);
305 } else {
306 GfxFillRect(outer.left, outer.top, inner.left - 1, inner.bottom, light); // Left
307 GfxFillRect(inner.left, outer.top, inner.right, inner.top - 1, light); // Top
308 GfxFillRect(inner.right + 1, outer.top, outer.right, inner.bottom, dark); // Right
309 GfxFillRect(outer.left, inner.bottom + 1, outer.right, outer.bottom, dark); // Bottom
310 interior = medium_dark;
311 }
312 if (!flags.Test(FrameFlag::BorderOnly)) {
313 GfxFillRect(inner.left, inner.top, inner.right, inner.bottom, interior); // Inner
314 }
315 }
316}
317
318void DrawSpriteIgnorePadding(SpriteID img, PaletteID pal, const Rect &r, StringAlignment align)
319{
320 Point offset;
321 Dimension d = GetSpriteSize(img, &offset);
322 d.width -= offset.x;
323 d.height -= offset.y;
324
325 Point p = GetAlignedPosition(r, d, align);
326 DrawSprite(img, pal, p.x - offset.x, p.y - offset.y);
327}
328
338static inline void DrawImageButtons(const Rect &r, WidgetType type, Colours colour, bool clicked, SpriteID img, StringAlignment align)
339{
340 assert(img != 0);
341 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
342
343 if ((type & WWT_MASK) == WWT_IMGBTN_2 && clicked) img++; // Show different image when clicked for #WWT_IMGBTN_2.
344 DrawSpriteIgnorePadding(img, PAL_NONE, r, align);
345}
346
357static inline void DrawLabel(const Rect &r, WidgetType type, bool clicked, TextColour colour, StringID str, StringAlignment align, FontSize fs)
358{
359 if (str == STR_NULL) return;
360 if ((type & WWT_MASK) == WWT_TEXTBTN_2 && clicked) str++;
361 Dimension d = GetStringBoundingBox(str, fs);
362 Point p = GetAlignedPosition(r, d, align);
363 DrawString(r.left, r.right, p.y, str, colour, align, false, fs);
364}
365
374static inline void DrawText(const Rect &r, TextColour colour, StringID str, StringAlignment align, FontSize fs)
375{
376 Dimension d = GetStringBoundingBox(str, fs);
377 Point p = GetAlignedPosition(r, d, align);
378 if (str != STR_NULL) DrawString(r.left, r.right, p.y, str, colour, align, false, fs);
379}
380
390static inline void DrawInset(const Rect &r, Colours colour, TextColour text_colour, StringID str, StringAlignment align, FontSize fs)
391{
392 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, {FrameFlag::Lowered, FrameFlag::Darkened});
393 if (str != STR_NULL) DrawString(r.Shrink(WidgetDimensions::scaled.inset), str, text_colour, align, false, fs);
394}
395
406static inline void DrawMatrix(const Rect &r, Colours colour, bool clicked, uint32_t num_columns, uint32_t num_rows, uint resize_x, uint resize_y)
407{
408 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
409
410 int column_width; // Width of a single column in the matrix.
411 if (num_columns == 0) {
412 column_width = resize_x;
413 num_columns = r.Width() / column_width;
414 } else {
415 column_width = r.Width() / num_columns;
416 }
417
418 int row_height; // Height of a single row in the matrix.
419 if (num_rows == 0) {
420 row_height = resize_y;
421 num_rows = r.Height() / row_height;
422 } else {
423 row_height = r.Height() / num_rows;
424 }
425
426 int col = GetColourGradient(colour, SHADE_LIGHTER);
427
428 int x = r.left;
429 for (int ctr = num_columns; ctr > 1; ctr--) {
430 x += column_width;
431 GfxFillRect(x, r.top + WidgetDimensions::scaled.bevel.top, x + WidgetDimensions::scaled.bevel.left - 1, r.bottom - WidgetDimensions::scaled.bevel.bottom, col);
432 }
433
434 x = r.top;
435 for (int ctr = num_rows; ctr > 1; ctr--) {
436 x += row_height;
438 }
439
440 col = GetColourGradient(colour, SHADE_NORMAL);
441
442 x = r.left - 1;
443 for (int ctr = num_columns; ctr > 1; ctr--) {
444 x += column_width;
445 GfxFillRect(x - WidgetDimensions::scaled.bevel.right + 1, r.top + WidgetDimensions::scaled.bevel.top, x, r.bottom - WidgetDimensions::scaled.bevel.bottom, col);
446 }
447
448 x = r.top - 1;
449 for (int ctr = num_rows; ctr > 1; ctr--) {
450 x += row_height;
451 GfxFillRect(r.left + WidgetDimensions::scaled.bevel.left, x - WidgetDimensions::scaled.bevel.bottom + 1, r.right - WidgetDimensions::scaled.bevel.right, x, col);
452 }
453}
454
464static inline void DrawVerticalScrollbar(const Rect &r, Colours colour, bool up_clicked, bool bar_dragged, bool down_clicked, const Scrollbar *scrollbar)
465{
466 int height = NWidgetScrollbar::GetVerticalDimension().height;
467
468 /* draw up/down buttons */
469 DrawImageButtons(r.WithHeight(height, false), NWID_VSCROLLBAR, colour, up_clicked, SPR_ARROW_UP, SA_CENTER);
470 DrawImageButtons(r.WithHeight(height, true), NWID_VSCROLLBAR, colour, down_clicked, SPR_ARROW_DOWN, SA_CENTER);
471
472 int c1 = GetColourGradient(colour, SHADE_DARK);
473 int c2 = GetColourGradient(colour, SHADE_LIGHTEST);
474
475 /* draw "shaded" background */
476 GfxFillRect(r.left, r.top + height, r.right, r.bottom - height, c2);
477 GfxFillRect(r.left, r.top + height, r.right, r.bottom - height, c1, FILLRECT_CHECKER);
478
479 /* track positions. These fractions are based on original 1x dimensions, but scale better. */
480 int left = r.left + r.Width() * 3 / 11; /* left track is positioned 3/11ths from the left */
481 int right = r.left + r.Width() * 8 / 11; /* right track is positioned 8/11ths from the left */
482 const uint8_t bl = WidgetDimensions::scaled.bevel.left;
483 const uint8_t br = WidgetDimensions::scaled.bevel.right;
484
485 /* draw shaded lines */
486 GfxFillRect(left - bl, r.top + height, left - 1, r.bottom - height, c1);
487 GfxFillRect(left, r.top + height, left + br - 1, r.bottom - height, c2);
488 GfxFillRect(right - bl, r.top + height, right - 1, r.bottom - height, c1);
489 GfxFillRect(right, r.top + height, right + br - 1, r.bottom - height, c2);
490
491 Point pt = HandleScrollbarHittest(scrollbar, r.top, r.bottom, false);
492 DrawFrameRect(r.left, pt.x, r.right, pt.y, colour, bar_dragged ? FrameFlag::Lowered : FrameFlags{});
493}
494
504static inline void DrawHorizontalScrollbar(const Rect &r, Colours colour, bool left_clicked, bool bar_dragged, bool right_clicked, const Scrollbar *scrollbar)
505{
506 int width = NWidgetScrollbar::GetHorizontalDimension().width;
507
508 DrawImageButtons(r.WithWidth(width, false), NWID_HSCROLLBAR, colour, left_clicked, SPR_ARROW_LEFT, SA_CENTER);
509 DrawImageButtons(r.WithWidth(width, true), NWID_HSCROLLBAR, colour, right_clicked, SPR_ARROW_RIGHT, SA_CENTER);
510
511 int c1 = GetColourGradient(colour, SHADE_DARK);
512 int c2 = GetColourGradient(colour, SHADE_LIGHTEST);
513
514 /* draw "shaded" background */
515 GfxFillRect(r.left + width, r.top, r.right - width, r.bottom, c2);
516 GfxFillRect(r.left + width, r.top, r.right - width, r.bottom, c1, FILLRECT_CHECKER);
517
518 /* track positions. These fractions are based on original 1x dimensions, but scale better. */
519 int top = r.top + r.Height() * 3 / 11; /* top track is positioned 3/11ths from the top */
520 int bottom = r.top + r.Height() * 8 / 11; /* bottom track is positioned 8/11ths from the top */
521 const uint8_t bt = WidgetDimensions::scaled.bevel.top;
522 const uint8_t bb = WidgetDimensions::scaled.bevel.bottom;
523
524 /* draw shaded lines */
525 GfxFillRect(r.left + width, top - bt, r.right - width, top - 1, c1);
526 GfxFillRect(r.left + width, top, r.right - width, top + bb - 1, c2);
527 GfxFillRect(r.left + width, bottom - bt, r.right - width, bottom - 1, c1);
528 GfxFillRect(r.left + width, bottom, r.right - width, bottom + bb - 1, c2);
529
530 /* draw actual scrollbar */
531 Point pt = HandleScrollbarHittest(scrollbar, r.left, r.right, true);
532 DrawFrameRect(pt.x, r.top, pt.y, r.bottom, colour, bar_dragged ? FrameFlag::Lowered : FrameFlags{});
533}
534
544static inline void DrawFrame(const Rect &r, Colours colour, TextColour text_colour, StringID str, StringAlignment align, FontSize fs)
545{
546 int x2 = r.left; // by default the left side is the left side of the widget
547
548 if (str != STR_NULL) x2 = DrawString(r.left + WidgetDimensions::scaled.frametext.left, r.right - WidgetDimensions::scaled.frametext.right, r.top, str, text_colour, align, false, fs);
549
550 int c1 = GetColourGradient(colour, SHADE_DARK);
551 int c2 = GetColourGradient(colour, SHADE_LIGHTEST);
552
553 /* If the frame has text, adjust the top bar to fit half-way through */
554 Rect inner = r.Shrink(ScaleGUITrad(1));
555 if (str != STR_NULL) inner.top = r.top + GetCharacterHeight(FS_NORMAL) / 2;
556
557 Rect outer = inner.Expand(WidgetDimensions::scaled.bevel);
558 Rect inside = inner.Shrink(WidgetDimensions::scaled.bevel);
559
560 if (_current_text_dir == TD_LTR) {
561 /* Line from upper left corner to start of text */
562 GfxFillRect(outer.left, outer.top, r.left + WidgetDimensions::scaled.frametext.left - WidgetDimensions::scaled.bevel.left - 1, inner.top - 1, c1);
563 GfxFillRect(inner.left, inner.top, r.left + WidgetDimensions::scaled.frametext.left - WidgetDimensions::scaled.bevel.left - 1, inside.top - 1, c2);
564
565 /* Line from end of text to upper right corner */
566 GfxFillRect(x2 + WidgetDimensions::scaled.bevel.right, outer.top, inner.right, inner.top - 1, c1);
567 GfxFillRect(x2 + WidgetDimensions::scaled.bevel.right, inner.top, inside.right, inside.top - 1, c2);
568 } else {
569 /* Line from upper left corner to start of text */
570 GfxFillRect(outer.left, outer.top, x2 - WidgetDimensions::scaled.bevel.left - 1, inner.top - 1, c1);
571 GfxFillRect(inner.left, inner.top, x2 - WidgetDimensions::scaled.bevel.left - 1, inside.top - 1, c2);
572
573 /* Line from end of text to upper right corner */
574 GfxFillRect(r.right - WidgetDimensions::scaled.frametext.right + WidgetDimensions::scaled.bevel.right, outer.top, inner.right, inner.top - 1, c1);
575 GfxFillRect(r.right - WidgetDimensions::scaled.frametext.right + WidgetDimensions::scaled.bevel.right, inner.top, inside.right, inside.top - 1, c2);
576 }
577
578 /* Line from upper left corner to bottom left corner */
579 GfxFillRect(outer.left, inner.top, inner.left - 1, inner.bottom, c1);
580 GfxFillRect(inner.left, inside.top, inside.left - 1, inside.bottom, c2);
581
582 /* Line from upper right corner to bottom right corner */
583 GfxFillRect(inside.right + 1, inner.top, inner.right, inside.bottom, c1);
584 GfxFillRect(inner.right + 1, outer.top, outer.right, inner.bottom, c2);
585
586 /* Line from bottom left corner to bottom right corner */
587 GfxFillRect(inner.left, inside.bottom + 1, inner.right, inner.bottom, c1);
588 GfxFillRect(outer.left, inner.bottom + 1, outer.right, outer.bottom, c2);
589}
590
597static inline void DrawShadeBox(const Rect &r, Colours colour, bool clicked)
598{
599 DrawImageButtons(r, WWT_SHADEBOX, colour, clicked, clicked ? SPR_WINDOW_SHADE: SPR_WINDOW_UNSHADE, SA_CENTER);
600}
601
608static inline void DrawStickyBox(const Rect &r, Colours colour, bool clicked)
609{
610 DrawImageButtons(r, WWT_STICKYBOX, colour, clicked, clicked ? SPR_PIN_UP : SPR_PIN_DOWN, SA_CENTER);
611}
612
619static inline void DrawDefSizeBox(const Rect &r, Colours colour, bool clicked)
620{
621 DrawImageButtons(r, WWT_DEFSIZEBOX, colour, clicked, SPR_WINDOW_DEFSIZE, SA_CENTER);
622}
623
630static inline void DrawDebugBox(const Rect &r, Colours colour, bool clicked)
631{
632 DrawImageButtons(r, WWT_DEBUGBOX, colour, clicked, SPR_WINDOW_DEBUG, SA_CENTER);
633}
634
643static inline void DrawResizeBox(const Rect &r, Colours colour, bool at_left, bool clicked, bool bevel)
644{
645 if (bevel) {
646 DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
647 } else if (clicked) {
649 }
650 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));
651}
652
658static inline void DrawCloseBox(const Rect &r, Colours colour)
659{
660 if (colour != COLOUR_WHITE) DrawFrameRect(r.left, r.top, r.right, r.bottom, colour, {});
661 Point offset;
662 Dimension d = GetSpriteSize(SPR_CLOSEBOX, &offset);
663 d.width -= offset.x;
664 d.height -= offset.y;
665 int s = ScaleSpriteTrad(1); /* Offset to account for shadow of SPR_CLOSEBOX */
666 DrawSprite(SPR_CLOSEBOX, (colour != COLOUR_WHITE ? TC_BLACK : TC_SILVER) | (1U << PALETTE_TEXT_RECOLOUR), CenterBounds(r.left, r.right, d.width - s) - offset.x, CenterBounds(r.top, r.bottom, d.height - s) - offset.y);
667}
668
679void DrawCaption(const Rect &r, Colours colour, Owner owner, TextColour text_colour, StringID str, StringAlignment align, FontSize fs)
680{
681 bool company_owned = owner < MAX_COMPANIES;
682
686
687 if (company_owned) {
689 }
690
691 if (str != STR_NULL) {
693 Point p = GetAlignedPosition(r, d, align);
694 DrawString(r.left + WidgetDimensions::scaled.captiontext.left, r.right - WidgetDimensions::scaled.captiontext.left, p.y, str, text_colour, align, false, fs);
695 }
696}
697
709static inline void DrawButtonDropdown(const Rect &r, Colours colour, bool clicked_button, bool clicked_dropdown, StringID str, StringAlignment align)
710{
711 int dd_width = NWidgetLeaf::dropdown_dimension.width;
712
713 if (_current_text_dir == TD_LTR) {
714 DrawFrameRect(r.left, r.top, r.right - dd_width, r.bottom, colour, clicked_button ? FrameFlag::Lowered : FrameFlags{});
715 DrawImageButtons(r.WithWidth(dd_width, true), WWT_DROPDOWN, colour, clicked_dropdown, SPR_ARROW_DOWN, SA_CENTER);
716 if (str != STR_NULL) {
717 DrawString(r.left + WidgetDimensions::scaled.dropdowntext.left, r.right - dd_width - WidgetDimensions::scaled.dropdowntext.right, CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), str, TC_BLACK, align);
718 }
719 } else {
720 DrawFrameRect(r.left + dd_width, r.top, r.right, r.bottom, colour, clicked_button ? FrameFlag::Lowered : FrameFlags{});
721 DrawImageButtons(r.WithWidth(dd_width, false), WWT_DROPDOWN, colour, clicked_dropdown, SPR_ARROW_DOWN, SA_CENTER);
722 if (str != STR_NULL) {
723 DrawString(r.left + dd_width + WidgetDimensions::scaled.dropdowntext.left, r.right - WidgetDimensions::scaled.dropdowntext.right, CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), str, TC_BLACK, align);
724 }
725 }
726}
727
732{
733 this->nested_root->Draw(this);
734
736 DrawFrameRect(0, 0, this->width - 1, this->height - 1, COLOUR_WHITE, FrameFlag::BorderOnly);
737 }
738
740 extern bool _window_highlight_colour;
741 for (const auto &pair : this->widget_lookup) {
742 const NWidgetBase *widget = pair.second;
743 if (!widget->IsHighlighted()) continue;
744
745 Rect outer = widget->GetCurrentRect();
747
748 int colour = _string_colourmap[_window_highlight_colour ? widget->GetHighlightColour() : TC_WHITE];
749
750 GfxFillRect(outer.left, outer.top, inner.left, inner.bottom, colour);
751 GfxFillRect(inner.left + 1, outer.top, inner.right - 1, inner.top, colour);
752 GfxFillRect(inner.right, outer.top, outer.right, inner.bottom, colour);
753 GfxFillRect(outer.left + 1, inner.bottom, outer.right - 1, outer.bottom, colour);
754 }
755 }
756}
757
764{
765 if (state == SBS_OFF) return;
766
767 assert(!this->widget_lookup.empty());
768 Rect r = this->GetWidget<NWidgetBase>(widget)->GetCurrentRect();
769
770 /* Sort button uses the same sprites as vertical scrollbar */
771 Dimension dim = NWidgetScrollbar::GetVerticalDimension();
772
773 DrawSpriteIgnorePadding(state == SBS_DOWN ? SPR_ARROW_DOWN : SPR_ARROW_UP, PAL_NONE, r.WithWidth(dim.width, _current_text_dir == TD_LTR), SA_CENTER);
774}
775
781{
782 return NWidgetScrollbar::GetVerticalDimension().width + 1;
783}
784
785bool _draw_widget_outlines;
786
787static void DrawOutline(const Window *, const NWidgetBase *wid)
788{
789 if (!_draw_widget_outlines || wid->current_x == 0 || wid->current_y == 0) return;
790
791 DrawRectOutline(wid->GetCurrentRect(), PC_WHITE, 1, 4);
792}
793
855
856/* ~NWidgetContainer() takes care of #next and #prev data members. */
857
903void NWidgetBase::SetDirty(const Window *w) const
904{
905 int abs_left = w->left + this->pos_x;
906 int abs_top = w->top + this->pos_y;
907 AddDirtyBlock(abs_left, abs_top, abs_left + this->current_x, abs_top + this->current_y);
908}
909
924{
925 return (this->type == tp) ? this : nullptr;
926}
927
928void NWidgetBase::ApplyAspectRatio()
929{
930 if (this->aspect_ratio == 0) return;
931 if (this->smallest_x == 0 || this->smallest_y == 0) return;
932
933 uint x = this->smallest_x;
934 uint y = this->smallest_y;
935 if (this->aspect_flags.Test(AspectFlag::ResizeX)) x = std::max(this->smallest_x, static_cast<uint>(this->smallest_y * std::abs(this->aspect_ratio)));
936 if (this->aspect_flags.Test(AspectFlag::ResizeY)) y = std::max(this->smallest_y, static_cast<uint>(this->smallest_x / std::abs(this->aspect_ratio)));
937
938 this->smallest_x = x;
939 this->smallest_y = y;
940}
941
942void NWidgetBase::AdjustPaddingForZoom()
943{
944 this->padding = ScaleGUITrad(this->uz_padding);
945}
946
954{
955 this->fill_x = fill_x;
956 this->fill_y = fill_y;
957}
958
965{
966 this->aspect_ratio = ratio;
967 this->aspect_flags = flags;
968}
969
976void NWidgetResizeBase::SetAspect(int x_ratio, int y_ratio, AspectFlags flags)
977{
978 this->SetAspect(static_cast<float>(x_ratio) / static_cast<float>(y_ratio), flags);
979}
980
981void NWidgetResizeBase::AdjustPaddingForZoom()
982{
983 if (!this->absolute) {
984 this->min_x = ScaleGUITrad(this->uz_min_x);
986 }
987 NWidgetBase::AdjustPaddingForZoom();
988}
989
995void NWidgetResizeBase::SetMinimalSize(uint min_x, uint min_y)
996{
997 this->uz_min_x = std::max(this->uz_min_x, min_x);
998 this->uz_min_y = std::max(this->uz_min_y, min_y);
999 this->min_x = ScaleGUITrad(this->uz_min_x);
1000 this->min_y = std::max(ScaleGUITrad(this->uz_min_y), this->uz_text_lines * GetCharacterHeight(this->uz_text_size) + ScaleGUITrad(this->uz_text_spacing));
1001}
1002
1009{
1010 this->absolute = true;
1011 this->min_x = std::max(this->min_x, min_x);
1012 this->min_y = std::max(this->min_y, min_y);
1013}
1014
1021void NWidgetResizeBase::SetMinimalTextLines(uint8_t min_lines, uint8_t spacing, FontSize size)
1022{
1023 this->uz_text_lines = min_lines;
1024 this->uz_text_spacing = spacing;
1025 this->uz_text_size = size;
1026 this->min_y = std::max(ScaleGUITrad(this->uz_min_y), this->uz_text_lines * GetCharacterHeight(this->uz_text_size) + ScaleGUITrad(this->uz_text_spacing));
1027}
1028
1034void NWidgetResizeBase::SetFill(uint fill_x, uint fill_y)
1035{
1036 this->fill_x = fill_x;
1037 this->fill_y = fill_y;
1038}
1039
1045void NWidgetResizeBase::SetResize(uint resize_x, uint resize_y)
1046{
1047 this->resize_x = resize_x;
1048 this->resize_y = resize_y;
1049}
1050
1058bool NWidgetResizeBase::UpdateMultilineWidgetSize(const std::string &str, int max_lines)
1059{
1060 int y = GetStringHeight(str, this->current_x);
1061 if (y > max_lines * GetCharacterHeight(FS_NORMAL)) {
1062 /* Text at the current width is too tall, so try to guess a better width. */
1064 d.height *= max_lines;
1065 d.width /= 2;
1066 return this->UpdateSize(d.width, d.height);
1067 }
1068 return this->UpdateVerticalSize(y);
1069}
1070
1078bool NWidgetResizeBase::UpdateSize(uint min_x, uint min_y)
1079{
1080 if (min_x == this->min_x && min_y == this->min_y) return false;
1081 this->min_x = min_x;
1082 this->min_y = min_y;
1083 return true;
1084}
1085
1093{
1094 if (min_y == this->min_y) return false;
1095 this->min_y = min_y;
1096 return true;
1097}
1098
1099void NWidgetResizeBase::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool)
1100{
1101 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1102}
1103
1114NWidgetCore::NWidgetCore(WidgetType tp, Colours colour, WidgetID index, uint fill_x, uint fill_y, const WidgetData &widget_data, StringID tool_tip) : NWidgetResizeBase(tp, fill_x, fill_y), index(index)
1115{
1116 this->colour = colour;
1117 this->widget_data = widget_data;
1118 this->SetToolTip(tool_tip);
1119 this->scrollbar_index = -1;
1120 this->text_colour = tp == WWT_CAPTION ? TC_WHITE : TC_BLACK;
1121 this->text_size = FS_NORMAL;
1122 this->align = SA_CENTER;
1123}
1124
1130{
1131 this->widget_data.string = string;
1132}
1133
1140{
1141 this->SetString(string);
1142 this->SetToolTip(tool_tip);
1143}
1144
1150{
1151 this->widget_data.sprite = sprite;
1152}
1153
1160{
1161 this->SetSprite(sprite);
1162 this->SetToolTip(tool_tip);
1163}
1164
1170void NWidgetCore::SetMatrixDimension(uint32_t columns, uint32_t rows)
1171{
1172 this->widget_data.matrix = { columns, rows };
1173}
1174
1180{
1181 this->widget_data.resize_widget_type = type;
1182}
1183
1190{
1191 this->text_colour = colour;
1192 this->text_size = size;
1193}
1194
1200{
1201 this->tool_tip = tool_tip;
1202}
1203
1209{
1210 return this->tool_tip;
1211}
1212
1218{
1219 this->align = align;
1220}
1221
1227{
1228 return this->widget_data.string;
1229}
1230
1236{
1237 return this->index;
1238}
1239
1245{
1246 return this->scrollbar_index;
1247}
1248
1250{
1251 if (this->index >= 0) widget_lookup[this->index] = this;
1252}
1253
1255{
1256 return (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) ? this : nullptr;
1257}
1258
1260{
1261 if (this->type == tp) return this;
1262 for (const auto &child_wid : this->children) {
1263 NWidgetBase *nwid = child_wid->GetWidgetOfType(tp);
1264 if (nwid != nullptr) return nwid;
1265 }
1266 return nullptr;
1267}
1268
1269void NWidgetContainer::AdjustPaddingForZoom()
1270{
1271 for (const auto &child_wid : this->children) {
1272 child_wid->AdjustPaddingForZoom();
1273 }
1274 NWidgetBase::AdjustPaddingForZoom();
1275}
1276
1281void NWidgetContainer::Add(std::unique_ptr<NWidgetBase> &&wid)
1282{
1283 assert(wid != nullptr);
1284 wid->parent = this;
1285 this->children.push_back(std::move(wid));
1286}
1287
1289{
1290 for (const auto &child_wid : this->children) {
1291 child_wid->FillWidgetLookup(widget_lookup);
1292 }
1293}
1294
1296{
1297 for (const auto &child_wid : this->children) {
1298 child_wid->Draw(w);
1299 }
1300
1301 DrawOutline(w, this);
1302}
1303
1305{
1306 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
1307
1308 for (const auto &child_wid : this->children) {
1309 NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
1310 if (nwid != nullptr) return nwid;
1311 }
1312 return nullptr;
1313}
1314
1321
1323{
1324 /* Zero size plane selected */
1325 if (this->shown_plane >= SZSP_BEGIN) {
1326 Dimension size = {0, 0};
1327 Dimension padding = {0, 0};
1328 Dimension fill = {(this->shown_plane == SZSP_HORIZONTAL), (this->shown_plane == SZSP_VERTICAL)};
1329 Dimension resize = {(this->shown_plane == SZSP_HORIZONTAL), (this->shown_plane == SZSP_VERTICAL)};
1330 /* Here we're primarily interested in the value of resize */
1331 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
1332
1333 this->smallest_x = size.width;
1334 this->smallest_y = size.height;
1335 this->fill_x = fill.width;
1336 this->fill_y = fill.height;
1337 this->resize_x = resize.width;
1338 this->resize_y = resize.height;
1339 this->ApplyAspectRatio();
1340 return;
1341 }
1342
1343 /* First sweep, recurse down and compute minimal size and filling. */
1344 this->smallest_x = 0;
1345 this->smallest_y = 0;
1346 this->fill_x = this->IsEmpty() ? 0 : 1;
1347 this->fill_y = this->IsEmpty() ? 0 : 1;
1348 this->resize_x = this->IsEmpty() ? 0 : 1;
1349 this->resize_y = this->IsEmpty() ? 0 : 1;
1350 for (const auto &child_wid : this->children) {
1351 child_wid->SetupSmallestSize(w);
1352
1353 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1354 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1355 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1356 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1357 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1358 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1359 this->ApplyAspectRatio();
1360 }
1361}
1362
1363void NWidgetStacked::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1364{
1365 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1366 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1367
1368 if (this->shown_plane >= SZSP_BEGIN) return;
1369
1370 for (const auto &child_wid : this->children) {
1371 uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1372 uint child_width = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1373 uint child_pos_x = (rtl ? child_wid->padding.right : child_wid->padding.left);
1374
1375 uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1376 uint child_height = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1377 uint child_pos_y = child_wid->padding.top;
1378
1379 child_wid->AssignSizePosition(sizing, x + child_pos_x, y + child_pos_y, child_width, child_height, rtl);
1380 }
1381}
1382
1384{
1385 /* We need to update widget_lookup later. */
1386 this->widget_lookup = &widget_lookup;
1387
1388 if (this->index >= 0) widget_lookup[this->index] = this;
1390 /* In case widget IDs are repeated, make sure Window::GetWidget works on displayed widgets. */
1391 if (static_cast<size_t>(this->shown_plane) < this->children.size()) this->children[shown_plane]->FillWidgetLookup(widget_lookup);
1392}
1393
1395{
1396 if (this->shown_plane >= SZSP_BEGIN) return;
1397
1398 assert(static_cast<size_t>(this->shown_plane) < this->children.size());
1399 this->children[shown_plane]->Draw(w);
1400 DrawOutline(w, this);
1401}
1402
1404{
1405 if (this->shown_plane >= SZSP_BEGIN) return nullptr;
1406
1407 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
1408
1409 if (static_cast<size_t>(this->shown_plane) >= this->children.size()) return nullptr;
1410 return this->children[shown_plane]->GetWidgetFromPos(x, y);
1411}
1412
1419{
1420 if (this->shown_plane == plane) return false;
1421 this->shown_plane = plane;
1422 /* In case widget IDs are repeated, make sure Window::GetWidget works on displayed widgets. */
1423 if (static_cast<size_t>(this->shown_plane) < this->children.size()) this->children[shown_plane]->FillWidgetLookup(*this->widget_lookup);
1424 return true;
1425}
1426
1428public:
1430
1431 void SetupSmallestSize(Window *w) override;
1432 void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override;
1433
1434 void Draw(const Window *w) override;
1435
1437};
1438
1439NWidgetLayer::NWidgetLayer(WidgetID index) : NWidgetContainer(NWID_LAYER), index(index) {}
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
1491NWidgetPIPContainer::NWidgetPIPContainer(WidgetType tp, NWidContainerFlags flags) : NWidgetContainer(tp)
1492{
1493 this->flags = flags;
1494}
1495
1496void NWidgetPIPContainer::AdjustPaddingForZoom()
1497{
1498 this->pip_pre = ScaleGUITrad(this->uz_pip_pre);
1499 this->pip_inter = ScaleGUITrad(this->uz_pip_inter);
1500 this->pip_post = ScaleGUITrad(this->uz_pip_post);
1501 NWidgetContainer::AdjustPaddingForZoom();
1502}
1503
1513void NWidgetPIPContainer::SetPIP(uint8_t pip_pre, uint8_t pip_inter, uint8_t pip_post)
1514{
1515 this->uz_pip_pre = pip_pre;
1516 this->uz_pip_inter = pip_inter;
1517 this->uz_pip_post = pip_post;
1518
1519 this->pip_pre = ScaleGUITrad(this->uz_pip_pre);
1520 this->pip_inter = ScaleGUITrad(this->uz_pip_inter);
1521 this->pip_post = ScaleGUITrad(this->uz_pip_post);
1522}
1523
1533void NWidgetPIPContainer::SetPIPRatio(uint8_t pip_ratio_pre, uint8_t pip_ratio_inter, uint8_t pip_ratio_post)
1534{
1535 this->pip_ratio_pre = pip_ratio_pre;
1536 this->pip_ratio_inter = pip_ratio_inter;
1537 this->pip_ratio_post = pip_ratio_post;
1538}
1539
1544
1546{
1547 this->smallest_x = 0; // Sum of minimal size of all children.
1548 this->smallest_y = 0; // Biggest child.
1549 this->fill_x = 0; // smallest non-zero child widget fill step.
1550 this->fill_y = 1; // smallest common child fill step.
1551 this->resize_x = 0; // smallest non-zero child widget resize step.
1552 this->resize_y = 1; // smallest common child resize step.
1553 this->gaps = 0;
1554
1555 /* 1a. Forward call, collect longest/widest child length. */
1556 uint longest = 0; // Longest child found.
1557 uint max_vert_fill = 0; // Biggest vertical fill step.
1558 for (const auto &child_wid : this->children) {
1559 child_wid->SetupSmallestSize(w);
1560 longest = std::max(longest, child_wid->smallest_x);
1561 max_vert_fill = std::max(max_vert_fill, child_wid->GetVerticalStepSize(ST_SMALLEST));
1562 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1563 if (child_wid->smallest_x != 0 || child_wid->fill_x != 0) this->gaps++;
1564 }
1565 if (this->gaps > 0) this->gaps--; // Number of gaps is number of widgets less one.
1566 /* 1b. Make the container higher if needed to accommodate all children nicely. */
1567 [[maybe_unused]] uint max_smallest = this->smallest_y + 3 * max_vert_fill; // Upper limit to computing smallest height.
1568 uint cur_height = this->smallest_y;
1569 for (;;) {
1570 for (const auto &child_wid : this->children) {
1571 uint step_size = child_wid->GetVerticalStepSize(ST_SMALLEST);
1572 uint child_height = child_wid->smallest_y + child_wid->padding.Vertical();
1573 if (step_size > 1 && child_height < cur_height) { // Small step sizes or already fitting children are not interesting.
1574 uint remainder = (cur_height - child_height) % step_size;
1575 if (remainder > 0) { // Child did not fit entirely, widen the container.
1576 cur_height += step_size - remainder;
1577 assert(cur_height < max_smallest); // Safeguard against infinite height expansion.
1578 /* Remaining children will adapt to the new cur_height, thus speeding up the computation. */
1579 }
1580 }
1581 }
1582 if (this->smallest_y == cur_height) break;
1583 this->smallest_y = cur_height; // Smallest height got changed, try again.
1584 }
1585 /* 2. For containers that must maintain equal width, extend child minimal size. */
1586 for (const auto &child_wid : this->children) {
1587 child_wid->smallest_y = this->smallest_y - child_wid->padding.Vertical();
1588 child_wid->ApplyAspectRatio();
1589 longest = std::max(longest, child_wid->smallest_x);
1590 }
1591 if (this->flags & NC_EQUALSIZE) {
1592 for (const auto &child_wid : this->children) {
1593 if (child_wid->fill_x == 1) child_wid->smallest_x = longest;
1594 }
1595 }
1596 /* 3. Compute smallest, fill, and resize values of the container. */
1597 for (const auto &child_wid : this->children) {
1598 this->smallest_x += child_wid->smallest_x + child_wid->padding.Horizontal();
1599 if (child_wid->fill_x > 0) {
1600 if (this->fill_x == 0 || this->fill_x > child_wid->fill_x) this->fill_x = child_wid->fill_x;
1601 }
1602 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1603
1604 if (child_wid->resize_x > 0) {
1605 if (this->resize_x == 0 || this->resize_x > child_wid->resize_x) this->resize_x = child_wid->resize_x;
1606 }
1607 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1608 }
1609 if (this->fill_x == 0 && this->pip_ratio_pre + this->pip_ratio_inter + this->pip_ratio_post > 0) this->fill_x = 1;
1610 /* 4. Increase by required PIP space. */
1611 this->smallest_x += this->pip_pre + this->gaps * this->pip_inter + this->pip_post;
1612}
1613
1614void NWidgetHorizontal::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1615{
1616 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1617
1618 /* Compute additional width given to us. */
1619 uint additional_length = given_width - (this->pip_pre + this->gaps * this->pip_inter + this->pip_post);
1620 for (const auto &child_wid : this->children) {
1621 if (child_wid->smallest_x != 0 || child_wid->fill_x != 0) additional_length -= child_wid->smallest_x + child_wid->padding.Horizontal();
1622 }
1623
1624 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1625
1626 /* In principle, the additional horizontal space is distributed evenly over the available resizable children. Due to step sizes, this may not always be feasible.
1627 * To make resizing work as good as possible, first children with biggest step sizes are done. These may get less due to rounding down.
1628 * 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
1629 * of the child with the smallest non-zero stepsize.
1630 *
1631 * 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
1632 * size and position, and directly call child->AssignSizePosition() with the computed values.
1633 * 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
1634 * then we call the child.
1635 */
1636
1637 /* First loop: Find biggest stepsize, find number of children that want a piece of the pie, handle vertical size for all children,
1638 * handle horizontal size for non-resizing children.
1639 */
1640 int num_changing_childs = 0; // Number of children that can change size.
1641 uint biggest_stepsize = 0;
1642 for (const auto &child_wid : this->children) {
1643 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1644 if (hor_step > 0) {
1645 if (!(flags & NC_BIGFIRST)) num_changing_childs++;
1646 biggest_stepsize = std::max(biggest_stepsize, hor_step);
1647 } else {
1648 child_wid->current_x = child_wid->smallest_x;
1649 }
1650
1651 uint vert_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetVerticalStepSize(sizing);
1652 child_wid->current_y = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1653 }
1654
1655 /* First.5 loop: count how many children are of the biggest step size. */
1656 if ((flags & NC_BIGFIRST) && biggest_stepsize > 0) {
1657 for (const auto &child_wid : this->children) {
1658 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1659 if (hor_step == biggest_stepsize) {
1660 num_changing_childs++;
1661 }
1662 }
1663 }
1664
1665 /* Second loop: Allocate the additional horizontal space over the resizing children, starting with the biggest resize steps. */
1666 while (biggest_stepsize > 0) {
1667 uint next_biggest_stepsize = 0;
1668 for (const auto &child_wid : this->children) {
1669 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1670 if (hor_step > biggest_stepsize) continue; // Already done
1671 if (hor_step == biggest_stepsize) {
1672 uint increment = additional_length / num_changing_childs;
1673 num_changing_childs--;
1674 if (hor_step > 1) increment -= increment % hor_step;
1675 child_wid->current_x = child_wid->smallest_x + increment;
1676 additional_length -= increment;
1677 continue;
1678 }
1679 next_biggest_stepsize = std::max(next_biggest_stepsize, hor_step);
1680 }
1681 biggest_stepsize = next_biggest_stepsize;
1682
1683 if (num_changing_childs == 0 && (flags & NC_BIGFIRST) && biggest_stepsize > 0) {
1684 /* Second.5 loop: count how many children are of the updated biggest step size. */
1685 for (const auto &child_wid : this->children) {
1686 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1687 if (hor_step == biggest_stepsize) {
1688 num_changing_childs++;
1689 }
1690 }
1691 }
1692 }
1693 assert(num_changing_childs == 0);
1694
1695 uint pre = this->pip_pre;
1696 uint inter = this->pip_inter;
1697
1698 if (additional_length > 0) {
1699 /* Allocate remaining space by pip ratios. If this doesn't round exactly, the unused space will fall into pip_post
1700 * which is never explicitly needed. */
1701 int r = this->pip_ratio_pre + this->gaps * this->pip_ratio_inter + this->pip_ratio_post;
1702 if (r > 0) {
1703 pre += this->pip_ratio_pre * additional_length / r;
1704 if (this->gaps > 0) inter += this->pip_ratio_inter * additional_length / r;
1705 }
1706 }
1707
1708 /* Third loop: Compute position and call the child. */
1709 uint position = rtl ? this->current_x - pre : pre; // Place to put next child relative to origin of the container.
1710 for (const auto &child_wid : this->children) {
1711 uint child_width = child_wid->current_x;
1712 uint child_x = x + (rtl ? position - child_width - child_wid->padding.left : position + child_wid->padding.left);
1713 uint child_y = y + child_wid->padding.top;
1714
1715 child_wid->AssignSizePosition(sizing, child_x, child_y, child_width, child_wid->current_y, rtl);
1716 if (child_wid->current_x != 0) {
1717 uint padded_child_width = child_width + child_wid->padding.Horizontal() + inter;
1718 position = rtl ? position - padded_child_width : position + padded_child_width;
1719 }
1720 }
1721}
1722
1728
1729void NWidgetHorizontalLTR::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool)
1730{
1731 NWidgetHorizontal::AssignSizePosition(sizing, x, y, given_width, given_height, false);
1732}
1733
1738
1740{
1741 this->smallest_x = 0; // Biggest child.
1742 this->smallest_y = 0; // Sum of minimal size of all children.
1743 this->fill_x = 1; // smallest common child fill step.
1744 this->fill_y = 0; // smallest non-zero child widget fill step.
1745 this->resize_x = 1; // smallest common child resize step.
1746 this->resize_y = 0; // smallest non-zero child widget resize step.
1747 this->gaps = 0;
1748
1749 /* 1a. Forward call, collect longest/widest child length. */
1750 uint highest = 0; // Highest child found.
1751 uint max_hor_fill = 0; // Biggest horizontal fill step.
1752 for (const auto &child_wid : this->children) {
1753 child_wid->SetupSmallestSize(w);
1754 highest = std::max(highest, child_wid->smallest_y);
1755 max_hor_fill = std::max(max_hor_fill, child_wid->GetHorizontalStepSize(ST_SMALLEST));
1756 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1757 if (child_wid->smallest_y != 0 || child_wid->fill_y != 0) this->gaps++;
1758 }
1759 if (this->gaps > 0) this->gaps--; // Number of gaps is number of widgets less one.
1760 /* 1b. Make the container wider if needed to accommodate all children nicely. */
1761 [[maybe_unused]] uint max_smallest = this->smallest_x + 3 * max_hor_fill; // Upper limit to computing smallest height.
1762 uint cur_width = this->smallest_x;
1763 for (;;) {
1764 for (const auto &child_wid : this->children) {
1765 uint step_size = child_wid->GetHorizontalStepSize(ST_SMALLEST);
1766 uint child_width = child_wid->smallest_x + child_wid->padding.Horizontal();
1767 if (step_size > 1 && child_width < cur_width) { // Small step sizes or already fitting children are not interesting.
1768 uint remainder = (cur_width - child_width) % step_size;
1769 if (remainder > 0) { // Child did not fit entirely, widen the container.
1770 cur_width += step_size - remainder;
1771 assert(cur_width < max_smallest); // Safeguard against infinite width expansion.
1772 /* Remaining children will adapt to the new cur_width, thus speeding up the computation. */
1773 }
1774 }
1775 }
1776 if (this->smallest_x == cur_width) break;
1777 this->smallest_x = cur_width; // Smallest width got changed, try again.
1778 }
1779 /* 2. For containers that must maintain equal width, extend children minimal size. */
1780 for (const auto &child_wid : this->children) {
1781 child_wid->smallest_x = this->smallest_x - child_wid->padding.Horizontal();
1782 child_wid->ApplyAspectRatio();
1783 highest = std::max(highest, child_wid->smallest_y);
1784 }
1785 if (this->flags & NC_EQUALSIZE) {
1786 for (const auto &child_wid : this->children) {
1787 if (child_wid->fill_y == 1) child_wid->smallest_y = highest;
1788 }
1789 }
1790 /* 3. Compute smallest, fill, and resize values of the container. */
1791 for (const auto &child_wid : this->children) {
1792 this->smallest_y += child_wid->smallest_y + child_wid->padding.Vertical();
1793 if (child_wid->fill_y > 0) {
1794 if (this->fill_y == 0 || this->fill_y > child_wid->fill_y) this->fill_y = child_wid->fill_y;
1795 }
1796 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1797
1798 if (child_wid->resize_y > 0) {
1799 if (this->resize_y == 0 || this->resize_y > child_wid->resize_y) this->resize_y = child_wid->resize_y;
1800 }
1801 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1802 }
1803 if (this->fill_y == 0 && this->pip_ratio_pre + this->pip_ratio_inter + this->pip_ratio_post > 0) this->fill_y = 1;
1804 /* 4. Increase by required PIP space. */
1805 this->smallest_y += this->pip_pre + this->gaps * this->pip_inter + this->pip_post;
1806}
1807
1808void NWidgetVertical::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1809{
1810 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1811
1812 /* Compute additional height given to us. */
1813 uint additional_length = given_height - (this->pip_pre + this->gaps * this->pip_inter + this->pip_post);
1814 for (const auto &child_wid : this->children) {
1815 if (child_wid->smallest_y != 0 || child_wid->fill_y != 0) additional_length -= child_wid->smallest_y + child_wid->padding.Vertical();
1816 }
1817
1818 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1819
1820 /* Like the horizontal container, the vertical container also distributes additional height evenly, starting with the children with the biggest resize steps.
1821 * It also stores computed widths and heights into current_x and current_y values of the child.
1822 */
1823
1824 /* 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. */
1825 int num_changing_childs = 0; // Number of children that can change size.
1826 uint biggest_stepsize = 0;
1827 for (const auto &child_wid : this->children) {
1828 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1829 if (vert_step > 0) {
1830 if (!(flags & NC_BIGFIRST)) num_changing_childs++;
1831 biggest_stepsize = std::max(biggest_stepsize, vert_step);
1832 } else {
1833 child_wid->current_y = child_wid->smallest_y;
1834 }
1835
1836 uint hor_step = (sizing == ST_SMALLEST) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1837 child_wid->current_x = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1838 }
1839
1840 /* First.5 loop: count how many children are of the biggest step size. */
1841 if ((this->flags & NC_BIGFIRST) && biggest_stepsize > 0) {
1842 for (const auto &child_wid : this->children) {
1843 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1844 if (vert_step == biggest_stepsize) {
1845 num_changing_childs++;
1846 }
1847 }
1848 }
1849
1850 /* Second loop: Allocate the additional vertical space over the resizing children, starting with the biggest resize steps. */
1851 while (biggest_stepsize > 0) {
1852 uint next_biggest_stepsize = 0;
1853 for (const auto &child_wid : this->children) {
1854 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1855 if (vert_step > biggest_stepsize) continue; // Already done
1856 if (vert_step == biggest_stepsize) {
1857 uint increment = additional_length / num_changing_childs;
1858 num_changing_childs--;
1859 if (vert_step > 1) increment -= increment % vert_step;
1860 child_wid->current_y = child_wid->smallest_y + increment;
1861 additional_length -= increment;
1862 continue;
1863 }
1864 next_biggest_stepsize = std::max(next_biggest_stepsize, vert_step);
1865 }
1866 biggest_stepsize = next_biggest_stepsize;
1867
1868 if (num_changing_childs == 0 && (flags & NC_BIGFIRST) && biggest_stepsize > 0) {
1869 /* Second.5 loop: count how many children are of the updated biggest step size. */
1870 for (const auto &child_wid : this->children) {
1871 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1872 if (vert_step == biggest_stepsize) {
1873 num_changing_childs++;
1874 }
1875 }
1876 }
1877 }
1878 assert(num_changing_childs == 0);
1879
1880 uint pre = this->pip_pre;
1881 uint inter = this->pip_inter;
1882
1883 if (additional_length > 0) {
1884 /* Allocate remaining space by pip ratios. If this doesn't round exactly, the unused space will fall into pip_post
1885 * which is never explicitly needed. */
1886 int r = this->pip_ratio_pre + this->gaps * this->pip_ratio_inter + this->pip_ratio_post;
1887 if (r > 0) {
1888 pre += this->pip_ratio_pre * additional_length / r;
1889 if (this->gaps > 0) inter += this->pip_ratio_inter * additional_length / r;
1890 }
1891 }
1892
1893 /* Third loop: Compute position and call the child. */
1894 uint position = pre; // Place to put next child relative to origin of the container.
1895 for (const auto &child_wid : this->children) {
1896 uint child_x = x + (rtl ? child_wid->padding.right : child_wid->padding.left);
1897 uint child_height = child_wid->current_y;
1898
1899 child_wid->AssignSizePosition(sizing, child_x, y + position + child_wid->padding.top, child_wid->current_x, child_height, rtl);
1900 if (child_wid->current_y != 0) {
1901 position += child_height + child_wid->padding.Vertical() + inter;
1902 }
1903 }
1904}
1905
1912{
1913 this->SetMinimalSize(width, height);
1914 this->SetResize(0, 0);
1915}
1916
1918{
1919 this->smallest_x = this->min_x;
1920 this->smallest_y = this->min_y;
1921 this->ApplyAspectRatio();
1922}
1923
1927
1929{
1930 /* Spacer widget is never normally visible. */
1931
1932 if (_draw_widget_outlines && this->current_x != 0 && this->current_y != 0) {
1933 /* Spacers indicate a potential design issue, so get extra highlighting. */
1934 GfxFillRect(this->GetCurrentRect(), PC_WHITE, FILLRECT_CHECKER);
1935
1936 DrawOutline(w, this);
1937 }
1938}
1939
1941{
1942 /* Spacer widget never need repainting. */
1943}
1944
1946{
1947 return nullptr;
1948}
1949
1950NWidgetMatrix::NWidgetMatrix(Colours colour, WidgetID index) : NWidgetPIPContainer(NWID_MATRIX, NC_EQUALSIZE), index(index), clicked(-1), count(-1)
1951{
1952 this->colour = colour;
1953}
1954
1960{
1961 this->clicked = clicked;
1962 if (this->clicked >= 0 && this->sb != nullptr && this->widgets_x != 0) {
1963 int vpos = (this->clicked / this->widgets_x) * this->widget_h; // Vertical position of the top.
1964 /* Need to scroll down -> Scroll to the bottom.
1965 * However, last entry has no 'this->pip_inter' underneath, and we must stay below this->sb->GetCount() */
1966 if (this->sb->GetPosition() < vpos) vpos += this->widget_h - this->pip_inter - 1;
1967 this->sb->ScrollTowards(vpos);
1968 }
1969}
1970
1977{
1978 this->count = count;
1979
1980 if (this->sb == nullptr || this->widgets_x == 0) return;
1981
1982 /* We need to get the number of pixels the matrix is high/wide.
1983 * So, determine the number of rows/columns based on the number of
1984 * columns/rows (one is constant/unscrollable).
1985 * Then multiply that by the height of a widget, and add the pre
1986 * and post spacing "offsets". */
1987 count = CeilDiv(count, this->sb->IsVertical() ? this->widgets_x : this->widgets_y);
1988 count *= (this->sb->IsVertical() ? this->children.front()->smallest_y : this->children.front()->smallest_x) + this->pip_inter;
1989 if (count > 0) count -= this->pip_inter; // We counted an inter too much in the multiplication above
1990 count += this->pip_pre + this->pip_post;
1991 this->sb->SetCount(count);
1992 this->sb->SetCapacity(this->sb->IsVertical() ? this->current_y : this->current_x);
1993 this->sb->SetStepSize(this->sb->IsVertical() ? this->widget_h : this->widget_w);
1994}
1995
2001{
2002 this->sb = sb;
2003}
2004
2010{
2011 return this->current_element;
2012}
2013
2015{
2016 assert(this->children.size() == 1);
2017
2018 this->children.front()->SetupSmallestSize(w);
2019
2020 Dimension padding = { (uint)this->pip_pre + this->pip_post, (uint)this->pip_pre + this->pip_post};
2021 Dimension size = {this->children.front()->smallest_x + padding.width, this->children.front()->smallest_y + padding.height};
2022 Dimension fill = {0, 0};
2023 Dimension resize = {this->pip_inter + this->children.front()->smallest_x, this->pip_inter + this->children.front()->smallest_y};
2024
2025 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
2026
2027 this->smallest_x = size.width;
2028 this->smallest_y = size.height;
2029 this->fill_x = fill.width;
2030 this->fill_y = fill.height;
2031 this->resize_x = resize.width;
2032 this->resize_y = resize.height;
2033 this->ApplyAspectRatio();
2034}
2035
2036void NWidgetMatrix::AssignSizePosition(SizingType, int x, int y, uint given_width, uint given_height, bool)
2037{
2038 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
2039
2040 this->pos_x = x;
2041 this->pos_y = y;
2042 this->current_x = given_width;
2043 this->current_y = given_height;
2044
2045 /* Determine the size of the widgets, and the number of visible widgets on each of the axis. */
2046 this->widget_w = this->children.front()->smallest_x + this->pip_inter;
2047 this->widget_h = this->children.front()->smallest_y + this->pip_inter;
2048
2049 /* Account for the pip_inter is between widgets, so we need to account for that when
2050 * the division assumes pip_inter is used for all widgets. */
2051 this->widgets_x = CeilDiv(this->current_x - this->pip_pre - this->pip_post + this->pip_inter, this->widget_w);
2052 this->widgets_y = CeilDiv(this->current_y - this->pip_pre - this->pip_post + this->pip_inter, this->widget_h);
2053
2054 /* When resizing, update the scrollbar's count. E.g. with a vertical
2055 * scrollbar becoming wider or narrower means the amount of rows in
2056 * the scrollbar becomes respectively smaller or higher. */
2057 this->SetCount(this->count);
2058}
2059
2061{
2062 if (this->index >= 0) widget_lookup[this->index] = this;
2064}
2065
2067{
2068 /* Falls outside of the matrix widget. */
2069 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
2070
2071 int start_x, start_y, base_offs_x, base_offs_y;
2072 this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
2073
2074 bool rtl = _current_text_dir == TD_RTL;
2075
2076 int widget_col = (rtl ?
2077 -x + (int)this->pip_post + this->pos_x + base_offs_x + this->widget_w - 1 - (int)this->pip_inter :
2078 x - (int)this->pip_pre - this->pos_x - base_offs_x
2079 ) / this->widget_w;
2080
2081 int widget_row = (y - base_offs_y - (int)this->pip_pre - this->pos_y) / this->widget_h;
2082
2083 this->current_element = (widget_row + start_y) * this->widgets_x + start_x + widget_col;
2084 if (this->current_element >= this->count) return nullptr;
2085
2086 NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->children.front().get());
2087 assert(child != nullptr);
2089 this->pos_x + (rtl ? this->pip_post - widget_col * this->widget_w : this->pip_pre + widget_col * this->widget_w) + base_offs_x,
2090 this->pos_y + this->pip_pre + widget_row * this->widget_h + base_offs_y,
2091 child->smallest_x, child->smallest_y, rtl);
2092
2093 return child->GetWidgetFromPos(x, y);
2094}
2095
2096/* virtual */ void NWidgetMatrix::Draw(const Window *w)
2097{
2098 /* Fill the background. */
2099 GfxFillRect(this->GetCurrentRect(), GetColourGradient(this->colour, SHADE_LIGHT));
2100
2101 /* Set up a clipping area for the previews. */
2102 bool rtl = _current_text_dir == TD_RTL;
2103 DrawPixelInfo tmp_dpi;
2104 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;
2105
2106 {
2107 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
2108
2109 /* Get the appropriate offsets so we can draw the right widgets. */
2110 NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->children.front().get());
2111 assert(child != nullptr);
2112 int start_x, start_y, base_offs_x, base_offs_y;
2113 this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
2114
2115 int offs_y = base_offs_y;
2116 for (int y = start_y; y < start_y + this->widgets_y + 1; y++, offs_y += this->widget_h) {
2117 /* Are we within bounds? */
2118 if (offs_y + child->smallest_y <= 0) continue;
2119 if (offs_y >= (int)this->current_y) break;
2120
2121 /* We've passed our amount of widgets. */
2122 if (y * this->widgets_x >= this->count) break;
2123
2124 int offs_x = base_offs_x;
2125 for (int x = start_x; x < start_x + this->widgets_x + 1; x++, offs_x += rtl ? -this->widget_w : this->widget_w) {
2126 /* Are we within bounds? */
2127 if (offs_x + child->smallest_x <= 0) continue;
2128 if (offs_x >= (int)this->current_x) continue;
2129
2130 /* Do we have this many widgets? */
2131 this->current_element = y * this->widgets_x + x;
2132 if (this->current_element >= this->count) break;
2133
2134 child->AssignSizePosition(ST_RESIZE, offs_x, offs_y, child->smallest_x, child->smallest_y, rtl);
2135 child->SetLowered(this->clicked == this->current_element);
2136 child->Draw(w);
2137 }
2138 }
2139 }
2140
2141 DrawOutline(w, this);
2142}
2143
2151void NWidgetMatrix::GetScrollOffsets(int &start_x, int &start_y, int &base_offs_x, int &base_offs_y)
2152{
2153 base_offs_x = _current_text_dir == TD_RTL ? this->widget_w * (this->widgets_x - 1) : 0;
2154 base_offs_y = 0;
2155 start_x = 0;
2156 start_y = 0;
2157 if (this->sb != nullptr) {
2158 if (this->sb->IsVertical()) {
2159 start_y = this->sb->GetPosition() / this->widget_h;
2160 base_offs_y += -this->sb->GetPosition() + start_y * this->widget_h;
2161 } else {
2162 start_x = this->sb->GetPosition() / this->widget_w;
2163 int sub_x = this->sb->GetPosition() - start_x * this->widget_w;
2164 if (_current_text_dir == TD_RTL) {
2165 base_offs_x += sub_x;
2166 } else {
2167 base_offs_x -= sub_x;
2168 }
2169 }
2170 }
2171}
2172
2182NWidgetBackground::NWidgetBackground(WidgetType tp, Colours colour, WidgetID index, std::unique_ptr<NWidgetPIPContainer> &&child) : NWidgetCore(tp, colour, index, 1, 1, {}, STR_NULL)
2183{
2184 assert(tp == WWT_PANEL || tp == WWT_INSET || tp == WWT_FRAME);
2185 this->child = std::move(child);
2186 if (this->child != nullptr) this->child->parent = this;
2187 this->SetAlignment(SA_TOP | SA_LEFT);
2188}
2189
2197void NWidgetBackground::Add(std::unique_ptr<NWidgetBase> &&nwid)
2198{
2199 if (this->child == nullptr) {
2200 this->child = std::make_unique<NWidgetVertical>();
2201 }
2202 nwid->parent = this->child.get();
2203 this->child->Add(std::move(nwid));
2204}
2205
2216void NWidgetBackground::SetPIP(uint8_t pip_pre, uint8_t pip_inter, uint8_t pip_post)
2217{
2218 if (this->child == nullptr) {
2219 this->child = std::make_unique<NWidgetVertical>();
2220 }
2221 this->child->parent = this;
2222 this->child->SetPIP(pip_pre, pip_inter, pip_post);
2223}
2224
2235void NWidgetBackground::SetPIPRatio(uint8_t pip_ratio_pre, uint8_t pip_ratio_inter, uint8_t pip_ratio_post)
2236{
2237 if (this->child == nullptr) {
2238 this->child = std::make_unique<NWidgetVertical>();
2239 }
2240 this->child->parent = this;
2241 this->child->SetPIPRatio(pip_ratio_pre, pip_ratio_inter, pip_ratio_post);
2242}
2243
2244void NWidgetBackground::AdjustPaddingForZoom()
2245{
2246 if (child != nullptr) child->AdjustPaddingForZoom();
2247 NWidgetCore::AdjustPaddingForZoom();
2248}
2249
2251{
2252 if (this->child != nullptr) {
2253 this->child->SetupSmallestSize(w);
2254
2255 this->smallest_x = this->child->smallest_x;
2256 this->smallest_y = this->child->smallest_y;
2257 this->fill_x = this->child->fill_x;
2258 this->fill_y = this->child->fill_y;
2259 this->resize_x = this->child->resize_x;
2260 this->resize_y = this->child->resize_y;
2261
2262 /* Don't apply automatic padding if there is no child widget. */
2263 if (w == nullptr) return;
2264
2265 if (this->type == WWT_FRAME) {
2266 /* Account for the size of the frame's text if that exists */
2267 this->child->padding = WidgetDimensions::scaled.frametext;
2268 this->child->padding.top = std::max<uint8_t>(WidgetDimensions::scaled.frametext.top, this->GetString() != STR_NULL ? GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.frametext.top / 2 : 0);
2269
2270 this->smallest_x += this->child->padding.Horizontal();
2271 this->smallest_y += this->child->padding.Vertical();
2272
2273 if (this->index >= 0) w->SetStringParameters(this->index);
2274 this->smallest_x = std::max(this->smallest_x, GetStringBoundingBox(this->GetString(), this->text_size).width + WidgetDimensions::scaled.frametext.Horizontal());
2275 } else if (this->type == WWT_INSET) {
2276 /* Apply automatic padding for bevel thickness. */
2277 this->child->padding = WidgetDimensions::scaled.bevel;
2278
2279 this->smallest_x += this->child->padding.Horizontal();
2280 this->smallest_y += this->child->padding.Vertical();
2281 }
2282 this->ApplyAspectRatio();
2283 } else {
2284 Dimension d = {this->min_x, this->min_y};
2285 Dimension fill = {this->fill_x, this->fill_y};
2286 Dimension resize = {this->resize_x, this->resize_y};
2287 if (w != nullptr) { // A non-nullptr window pointer acts as switch to turn dynamic widget size on.
2288 if (this->type == WWT_FRAME || this->type == WWT_INSET) {
2289 if (this->index >= 0) w->SetStringParameters(this->index);
2290 Dimension background = GetStringBoundingBox(this->GetString(), this->text_size);
2291 background.width += (this->type == WWT_FRAME) ? (WidgetDimensions::scaled.frametext.Horizontal()) : (WidgetDimensions::scaled.inset.Horizontal());
2292 d = maxdim(d, background);
2293 }
2294 if (this->index >= 0) {
2296 switch (this->type) {
2297 default: NOT_REACHED();
2301 }
2302 w->UpdateWidgetSize(this->index, d, padding, fill, resize);
2303 }
2304 }
2305 this->smallest_x = d.width;
2306 this->smallest_y = d.height;
2307 this->fill_x = fill.width;
2308 this->fill_y = fill.height;
2309 this->resize_x = resize.width;
2310 this->resize_y = resize.height;
2311 this->ApplyAspectRatio();
2312 }
2313}
2314
2315void NWidgetBackground::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
2316{
2317 this->StoreSizePosition(sizing, x, y, given_width, given_height);
2318
2319 if (this->child != nullptr) {
2320 uint x_offset = (rtl ? this->child->padding.right : this->child->padding.left);
2321 uint width = given_width - this->child->padding.Horizontal();
2322 uint height = given_height - this->child->padding.Vertical();
2323 this->child->AssignSizePosition(sizing, x + x_offset, y + this->child->padding.top, width, height, rtl);
2324 }
2325}
2326
2328{
2329 if (this->index >= 0) widget_lookup[this->index] = this;
2330 if (this->child != nullptr) this->child->FillWidgetLookup(widget_lookup);
2331}
2332
2334{
2335 if (this->current_x == 0 || this->current_y == 0) return;
2336
2337 Rect r = this->GetCurrentRect();
2338
2339 const DrawPixelInfo *dpi = _cur_dpi;
2340 if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2341
2342 switch (this->type) {
2343 case WWT_PANEL:
2344 DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, this->IsLowered() ? FrameFlag::Lowered : FrameFlags{});
2345 break;
2346
2347 case WWT_FRAME:
2348 if (this->index >= 0) w->SetStringParameters(this->index);
2349 DrawFrame(r, this->colour, this->text_colour, this->GetString(), this->align, this->text_size);
2350 break;
2351
2352 case WWT_INSET:
2353 if (this->index >= 0) w->SetStringParameters(this->index);
2354 DrawInset(r, this->colour, this->text_colour, this->GetString(), this->align, this->text_size);
2355 break;
2356
2357 default:
2358 NOT_REACHED();
2359 }
2360
2361 if (this->index >= 0) w->DrawWidget(r, this->index);
2362 if (this->child != nullptr) this->child->Draw(w);
2363
2364 if (this->IsDisabled()) {
2366 }
2367
2368 DrawOutline(w, this);
2369}
2370
2372{
2373 NWidgetCore *nwid = nullptr;
2374 if (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) {
2375 if (this->child != nullptr) nwid = this->child->GetWidgetFromPos(x, y);
2376 if (nwid == nullptr) nwid = this;
2377 }
2378 return nwid;
2379}
2380
2382{
2383 NWidgetBase *nwid = nullptr;
2384 if (this->child != nullptr) nwid = this->child->GetWidgetOfType(tp);
2385 if (nwid == nullptr && this->type == tp) nwid = this;
2386 return nwid;
2387}
2388
2389NWidgetViewport::NWidgetViewport(WidgetID index) : NWidgetCore(NWID_VIEWPORT, INVALID_COLOUR, index, 1, 1, {}, STR_NULL)
2390{
2391}
2392
2394{
2395 this->smallest_x = this->min_x;
2396 this->smallest_y = this->min_y;
2397 this->ApplyAspectRatio();
2398}
2399
2401{
2402 if (this->current_x == 0 || this->current_y == 0) return;
2403
2404 if (this->disp_flags & ND_NO_TRANSPARENCY) {
2406 _transparency_opt &= (1 << TO_SIGNS) | (1 << TO_TEXT); // Disable all transparency, except textual stuff
2407 w->DrawViewport();
2408 _transparency_opt = to_backup;
2409 } else {
2410 w->DrawViewport();
2411 }
2412
2413 /* Optionally shade the viewport. */
2414 if (this->disp_flags & (ND_SHADE_GREY | ND_SHADE_DIMMED)) {
2416 }
2417
2418 DrawOutline(w, this);
2419}
2420
2427void NWidgetViewport::InitializeViewport(Window *w, std::variant<TileIndex, VehicleID> focus, ZoomLevel zoom)
2428{
2429 InitializeWindowViewport(w, this->pos_x, this->pos_y, this->current_x, this->current_y, focus, zoom);
2430}
2431
2437{
2438 Viewport *vp = w->viewport;
2439 if (vp != nullptr) {
2440 vp->left = w->left + this->pos_x;
2441 vp->top = w->top + this->pos_y;
2442 vp->width = this->current_x;
2443 vp->height = this->current_y;
2444
2445 vp->virtual_width = ScaleByZoom(vp->width, vp->zoom);
2446 vp->virtual_height = ScaleByZoom(vp->height, vp->zoom);
2447 }
2448}
2449
2459Scrollbar::size_type Scrollbar::GetScrolledRowFromWidget(int clickpos, const Window * const w, WidgetID widget, int padding, int line_height) const
2460{
2461 int pos = w->GetRowFromWidget(clickpos, widget, padding, line_height);
2462 if (pos != INT_MAX) pos += this->GetPosition();
2463 return (pos < 0 || pos >= this->GetCount()) ? Scrollbar::npos : pos;
2464}
2465
2480EventState Scrollbar::UpdateListPositionOnKeyPress(int &list_position, uint16_t keycode) const
2481{
2482 int new_pos = list_position;
2483 switch (keycode) {
2484 case WKC_UP:
2485 /* scroll up by one */
2486 new_pos--;
2487 break;
2488
2489 case WKC_DOWN:
2490 /* scroll down by one */
2491 new_pos++;
2492 break;
2493
2494 case WKC_PAGEUP:
2495 /* scroll up a page */
2496 new_pos -= this->GetCapacity();
2497 break;
2498
2499 case WKC_PAGEDOWN:
2500 /* scroll down a page */
2501 new_pos += this->GetCapacity();
2502 break;
2503
2504 case WKC_HOME:
2505 /* jump to beginning */
2506 new_pos = 0;
2507 break;
2508
2509 case WKC_END:
2510 /* jump to end */
2511 new_pos = this->GetCount() - 1;
2512 break;
2513
2514 default:
2515 return ES_NOT_HANDLED;
2516 }
2517
2518 /* If there are no elements, there is nothing to scroll/update. */
2519 if (this->GetCount() != 0) {
2520 list_position = Clamp(new_pos, 0, this->GetCount() - 1);
2521 }
2522 return ES_HANDLED;
2523}
2524
2525
2534{
2535 NWidgetBase *nwid = w->GetWidget<NWidgetBase>(widget);
2536 if (this->IsVertical()) {
2537 this->SetCapacity(((int)nwid->current_y - padding) / (int)nwid->resize_y);
2538 } else {
2539 this->SetCapacity(((int)nwid->current_x - padding) / (int)nwid->resize_x);
2540 }
2541}
2542
2550Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
2551{
2552 const int count = sb.GetCount() * resize_step;
2553 const int position = sb.GetPosition() * resize_step;
2554
2555 if (sb.IsVertical()) {
2556 r.top -= position;
2557 r.bottom = r.top + count;
2558 } else {
2559 bool rtl = _current_text_dir == TD_RTL;
2560 if (rtl) {
2561 r.right += position;
2562 r.left = r.right - count;
2563 } else {
2564 r.left -= position;
2565 r.right = r.left + count;
2566 }
2567 }
2568
2569 return r;
2570}
2571
2578NWidgetScrollbar::NWidgetScrollbar(WidgetType tp, Colours colour, WidgetID index) : NWidgetCore(tp, colour, index, 1, 1, {}, STR_NULL), Scrollbar(tp != NWID_HSCROLLBAR)
2579{
2580 assert(tp == NWID_HSCROLLBAR || tp == NWID_VSCROLLBAR);
2581
2582 switch (this->type) {
2583 case NWID_HSCROLLBAR:
2584 this->SetResize(1, 0);
2585 this->SetFill(1, 0);
2586 this->SetToolTip(STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST);
2587 break;
2588
2589 case NWID_VSCROLLBAR:
2590 this->SetResize(0, 1);
2591 this->SetFill(0, 1);
2592 this->SetToolTip(STR_TOOLTIP_VSCROLL_BAR_SCROLLS_LIST);
2593 break;
2594
2595 default: NOT_REACHED();
2596 }
2597}
2598
2600{
2601 this->min_x = 0;
2602 this->min_y = 0;
2603
2604 switch (this->type) {
2605 case NWID_HSCROLLBAR:
2606 this->SetMinimalSizeAbsolute(NWidgetScrollbar::GetHorizontalDimension().width * 3, NWidgetScrollbar::GetHorizontalDimension().height);
2607 break;
2608
2609 case NWID_VSCROLLBAR:
2610 this->SetMinimalSizeAbsolute(NWidgetScrollbar::GetVerticalDimension().width, NWidgetScrollbar::GetVerticalDimension().height * 3);
2611 break;
2612
2613 default: NOT_REACHED();
2614 }
2615
2616 this->smallest_x = this->min_x;
2617 this->smallest_y = this->min_y;
2618}
2619
2621{
2622 if (this->current_x == 0 || this->current_y == 0) return;
2623
2624 Rect r = this->GetCurrentRect();
2625
2626 const DrawPixelInfo *dpi = _cur_dpi;
2627 if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2628
2629 bool up_lowered = HasBit(this->disp_flags, NDB_SCROLLBAR_UP);
2630 bool down_lowered = HasBit(this->disp_flags, NDB_SCROLLBAR_DOWN);
2631 bool middle_lowered = !(this->disp_flags & ND_SCROLLBAR_BTN) && w->mouse_capture_widget == this->index;
2632
2633 if (this->type == NWID_HSCROLLBAR) {
2634 DrawHorizontalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2635 } else {
2636 DrawVerticalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2637 }
2638
2639 if (this->IsDisabled()) {
2641 }
2642
2643 DrawOutline(w, this);
2644}
2645
2646/* static */ void NWidgetScrollbar::InvalidateDimensionCache()
2647{
2648 vertical_dimension.width = vertical_dimension.height = 0;
2649 horizontal_dimension.width = horizontal_dimension.height = 0;
2650}
2651
2652/* static */ Dimension NWidgetScrollbar::GetVerticalDimension()
2653{
2654 if (vertical_dimension.width == 0) {
2655 vertical_dimension = maxdim(GetScaledSpriteSize(SPR_ARROW_UP), GetScaledSpriteSize(SPR_ARROW_DOWN));
2658 }
2659 return vertical_dimension;
2660}
2661
2662/* static */ Dimension NWidgetScrollbar::GetHorizontalDimension()
2663{
2664 if (horizontal_dimension.width == 0) {
2665 horizontal_dimension = maxdim(GetScaledSpriteSize(SPR_ARROW_LEFT), GetScaledSpriteSize(SPR_ARROW_RIGHT));
2668 }
2669 return horizontal_dimension;
2670}
2671
2674
2677{
2678 shadebox_dimension.width = shadebox_dimension.height = 0;
2679 debugbox_dimension.width = debugbox_dimension.height = 0;
2680 defsizebox_dimension.width = defsizebox_dimension.height = 0;
2681 stickybox_dimension.width = stickybox_dimension.height = 0;
2682 resizebox_dimension.width = resizebox_dimension.height = 0;
2683 closebox_dimension.width = closebox_dimension.height = 0;
2684 dropdown_dimension.width = dropdown_dimension.height = 0;
2685}
2686
2694
2703NWidgetLeaf::NWidgetLeaf(WidgetType tp, Colours colour, WidgetID index, const WidgetData &data, StringID tip) : NWidgetCore(tp, colour, index, 1, 1, data, tip)
2704{
2705 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);
2706 this->min_x = 0;
2707 this->min_y = 0;
2708 this->SetResize(0, 0);
2709
2710 switch (tp) {
2711 case WWT_EMPTY:
2712 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_EMPTY should not have a colour");
2713 break;
2714
2715 case WWT_TEXT:
2716 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_TEXT should not have a colour");
2717 this->SetFill(0, 0);
2719 break;
2720
2721 case WWT_LABEL:
2722 if (colour != INVALID_COLOUR) [[unlikely]] throw std::runtime_error("WWT_LABEL should not have a colour");
2723 [[fallthrough]];
2724
2725 case WWT_PUSHBTN:
2726 case WWT_IMGBTN:
2727 case WWT_PUSHIMGBTN:
2728 case WWT_IMGBTN_2:
2729 case WWT_TEXTBTN:
2730 case WWT_PUSHTXTBTN:
2731 case WWT_TEXTBTN_2:
2732 case WWT_MATRIX:
2734 case NWID_PUSHBUTTON_DROPDOWN:
2735 this->SetFill(0, 0);
2736 break;
2737
2738 case WWT_ARROWBTN:
2739 case WWT_PUSHARROWBTN:
2740 this->SetFill(0, 0);
2741 this->SetAspect(WidgetDimensions::ASPECT_LEFT_RIGHT_BUTTON);
2742 break;
2743
2744 case WWT_EDITBOX:
2745 this->SetFill(0, 0);
2746 break;
2747
2748 case WWT_CAPTION:
2749 this->SetFill(1, 0);
2750 this->SetResize(1, 0);
2752 this->SetMinimalTextLines(1, WidgetDimensions::unscaled.captiontext.Vertical(), FS_NORMAL);
2753 this->SetToolTip(STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
2754 break;
2755
2756 case WWT_STICKYBOX:
2757 this->SetFill(0, 0);
2759 this->SetToolTip(STR_TOOLTIP_STICKY);
2760 this->SetAspect(this->min_x, this->min_y);
2761 break;
2762
2763 case WWT_SHADEBOX:
2764 this->SetFill(0, 0);
2766 this->SetToolTip(STR_TOOLTIP_SHADE);
2767 this->SetAspect(this->min_x, this->min_y);
2768 break;
2769
2770 case WWT_DEBUGBOX:
2771 this->SetFill(0, 0);
2773 this->SetToolTip(STR_TOOLTIP_DEBUG);
2774 this->SetAspect(this->min_x, this->min_y);
2775 break;
2776
2777 case WWT_DEFSIZEBOX:
2778 this->SetFill(0, 0);
2780 this->SetToolTip(STR_TOOLTIP_DEFSIZE);
2781 this->SetAspect(this->min_x, this->min_y);
2782 break;
2783
2784 case WWT_RESIZEBOX:
2785 this->SetFill(0, 0);
2788 this->SetToolTip(STR_TOOLTIP_RESIZE);
2789 break;
2790
2791 case WWT_CLOSEBOX:
2792 this->SetFill(0, 0);
2794 this->SetToolTip(STR_TOOLTIP_CLOSE_WINDOW);
2795 this->SetAspect(this->min_x, this->min_y);
2796 break;
2797
2798 case WWT_DROPDOWN:
2799 this->SetFill(0, 0);
2801 this->SetAlignment(SA_TOP | SA_LEFT);
2802 break;
2803
2804 default:
2805 NOT_REACHED();
2806 }
2807}
2808
2810{
2811 Dimension padding = {0, 0};
2812 Dimension size = {this->min_x, this->min_y};
2813 Dimension fill = {this->fill_x, this->fill_y};
2814 Dimension resize = {this->resize_x, this->resize_y};
2815 switch (this->type) {
2816 case WWT_EMPTY: {
2817 break;
2818 }
2819 case WWT_MATRIX: {
2821 break;
2822 }
2823 case WWT_SHADEBOX: {
2825 if (NWidgetLeaf::shadebox_dimension.width == 0) {
2826 NWidgetLeaf::shadebox_dimension = maxdim(GetScaledSpriteSize(SPR_WINDOW_SHADE), GetScaledSpriteSize(SPR_WINDOW_UNSHADE));
2829 }
2831 break;
2832 }
2833 case WWT_DEBUGBOX:
2836 if (NWidgetLeaf::debugbox_dimension.width == 0) {
2840 }
2842 } else {
2843 /* If the setting is disabled we don't want to see it! */
2844 size.width = 0;
2845 fill.width = 0;
2846 resize.width = 0;
2847 }
2848 break;
2849
2850 case WWT_STICKYBOX: {
2852 if (NWidgetLeaf::stickybox_dimension.width == 0) {
2856 }
2858 break;
2859 }
2860
2861 case WWT_DEFSIZEBOX: {
2863 if (NWidgetLeaf::defsizebox_dimension.width == 0) {
2867 }
2869 break;
2870 }
2871
2872 case WWT_RESIZEBOX: {
2874 if (NWidgetLeaf::resizebox_dimension.width == 0) {
2875 NWidgetLeaf::resizebox_dimension = maxdim(GetScaledSpriteSize(SPR_WINDOW_RESIZE_LEFT), GetScaledSpriteSize(SPR_WINDOW_RESIZE_RIGHT));
2878 }
2880 break;
2881 }
2882 case WWT_EDITBOX: {
2883 Dimension sprite_size = GetScaledSpriteSize(_current_text_dir == TD_RTL ? SPR_IMG_DELETE_RIGHT : SPR_IMG_DELETE_LEFT);
2884 size.width = std::max(size.width, ScaleGUITrad(30) + sprite_size.width);
2885 size.height = std::max(sprite_size.height, GetStringBoundingBox("_").height + WidgetDimensions::scaled.framerect.Vertical());
2886 }
2887 [[fallthrough]];
2888 case WWT_PUSHBTN: {
2890 break;
2891 }
2892 case WWT_IMGBTN:
2893 case WWT_IMGBTN_2:
2894 case WWT_PUSHIMGBTN: {
2896 Dimension d2 = GetScaledSpriteSize(this->widget_data.sprite);
2897 if (this->type == WWT_IMGBTN_2) d2 = maxdim(d2, GetScaledSpriteSize(this->widget_data.sprite + 1));
2898 d2.width += padding.width;
2899 d2.height += padding.height;
2900 size = maxdim(size, d2);
2901 break;
2902 }
2903 case WWT_ARROWBTN:
2904 case WWT_PUSHARROWBTN: {
2906 Dimension d2 = maxdim(GetScaledSpriteSize(SPR_ARROW_LEFT), GetScaledSpriteSize(SPR_ARROW_RIGHT));
2907 d2.width += padding.width;
2908 d2.height += padding.height;
2909 size = maxdim(size, d2);
2910 break;
2911 }
2912
2913 case WWT_CLOSEBOX: {
2915 if (NWidgetLeaf::closebox_dimension.width == 0) {
2919 }
2921 break;
2922 }
2923 case WWT_TEXTBTN:
2924 case WWT_PUSHTXTBTN:
2925 case WWT_TEXTBTN_2: {
2927 if (this->index >= 0) w->SetStringParameters(this->index);
2929 d2.width += padding.width;
2930 d2.height += padding.height;
2931 size = maxdim(size, d2);
2932 break;
2933 }
2934 case WWT_LABEL:
2935 case WWT_TEXT: {
2936 if (this->index >= 0) w->SetStringParameters(this->index);
2937 size = maxdim(size, GetStringBoundingBox(this->GetString(), this->text_size));
2938 break;
2939 }
2940 case WWT_CAPTION: {
2942 if (this->index >= 0) w->SetStringParameters(this->index);
2944 d2.width += padding.width;
2945 d2.height += padding.height;
2946 size = maxdim(size, d2);
2947 break;
2948 }
2949 case WWT_DROPDOWN:
2951 case NWID_PUSHBUTTON_DROPDOWN: {
2952 if (NWidgetLeaf::dropdown_dimension.width == 0) {
2956 }
2958 if (this->index >= 0) w->SetStringParameters(this->index);
2960 d2.width += padding.width;
2961 d2.height = std::max(d2.height + padding.height, NWidgetLeaf::dropdown_dimension.height);
2962 size = maxdim(size, d2);
2963 break;
2964 }
2965 default:
2966 NOT_REACHED();
2967 }
2968
2969 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
2970
2971 this->smallest_x = size.width;
2972 this->smallest_y = size.height;
2973 this->fill_x = fill.width;
2974 this->fill_y = fill.height;
2975 this->resize_x = resize.width;
2976 this->resize_y = resize.height;
2977 this->ApplyAspectRatio();
2978}
2979
2981{
2982 if (this->current_x == 0 || this->current_y == 0) return;
2983
2984 /* Setup a clipping rectangle... for WWT_EMPTY or WWT_TEXT, an extra scaled pixel is allowed in case text shadow encroaches. */
2985 int extra = (this->type == WWT_EMPTY || this->type == WWT_TEXT) ? ScaleGUITrad(1) : 0;
2986 DrawPixelInfo new_dpi;
2987 if (!FillDrawPixelInfo(&new_dpi, this->pos_x, this->pos_y, this->current_x + extra, this->current_y + extra)) return;
2988 /* ...but keep coordinates relative to the window. */
2989 new_dpi.left += this->pos_x;
2990 new_dpi.top += this->pos_y;
2991
2992 AutoRestoreBackup dpi_backup(_cur_dpi, &new_dpi);
2993
2994 Rect r = this->GetCurrentRect();
2995
2996 bool clicked = this->IsLowered();
2997 switch (this->type) {
2998 case WWT_EMPTY:
2999 /* WWT_EMPTY used as a spacer indicates a potential design issue. */
3000 if (this->index == -1 && _draw_widget_outlines) {
3002 }
3003 break;
3004
3005 case WWT_PUSHBTN:
3006 DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
3007 break;
3008
3009 case WWT_IMGBTN:
3010 case WWT_PUSHIMGBTN:
3011 case WWT_IMGBTN_2:
3012 DrawImageButtons(r, this->type, this->colour, clicked, this->widget_data.sprite, this->align);
3013 break;
3014
3015 case WWT_TEXTBTN:
3016 case WWT_PUSHTXTBTN:
3017 case WWT_TEXTBTN_2:
3018 if (this->index >= 0) w->SetStringParameters(this->index);
3019 DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FrameFlag::Lowered : FrameFlags{});
3020 DrawLabel(r, this->type, clicked, this->text_colour, this->GetString(), this->align, this->text_size);
3021 break;
3022
3023 case WWT_ARROWBTN:
3024 case WWT_PUSHARROWBTN: {
3025 SpriteID sprite;
3026 switch (this->widget_data.arrow_widget_type) {
3027 case AWV_DECREASE: sprite = _current_text_dir != TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
3028 case AWV_INCREASE: sprite = _current_text_dir == TD_RTL ? SPR_ARROW_LEFT : SPR_ARROW_RIGHT; break;
3029 case AWV_LEFT: sprite = SPR_ARROW_LEFT; break;
3030 case AWV_RIGHT: sprite = SPR_ARROW_RIGHT; break;
3031 default: NOT_REACHED();
3032 }
3033 DrawImageButtons(r, WWT_PUSHIMGBTN, this->colour, clicked, sprite, this->align);
3034 break;
3035 }
3036
3037 case WWT_LABEL:
3038 if (this->index >= 0) w->SetStringParameters(this->index);
3039 DrawLabel(r, this->type, clicked, this->text_colour, this->GetString(), this->align, this->text_size);
3040 break;
3041
3042 case WWT_TEXT:
3043 if (this->index >= 0) w->SetStringParameters(this->index);
3044 DrawText(r, this->text_colour, this->GetString(), this->align, this->text_size);
3045 break;
3046
3047 case WWT_MATRIX:
3048 DrawMatrix(r, this->colour, clicked, this->widget_data.matrix.width, this->widget_data.matrix.height, this->resize_x, this->resize_y);
3049 break;
3050
3051 case WWT_EDITBOX: {
3052 const QueryString *query = w->GetQueryString(this->index);
3053 if (query != nullptr) query->DrawEditBox(w, this->index);
3054 break;
3055 }
3056
3057 case WWT_CAPTION:
3058 if (this->index >= 0) w->SetStringParameters(this->index);
3059 DrawCaption(r, this->colour, w->owner, this->text_colour, this->GetString(), this->align, this->text_size);
3060 break;
3061
3062 case WWT_SHADEBOX:
3063 DrawShadeBox(r, this->colour, w->IsShaded());
3064 break;
3065
3066 case WWT_DEBUGBOX:
3067 DrawDebugBox(r, this->colour, clicked);
3068 break;
3069
3070 case WWT_STICKYBOX:
3072 break;
3073
3074 case WWT_DEFSIZEBOX:
3075 DrawDefSizeBox(r, this->colour, clicked);
3076 break;
3077
3078 case WWT_RESIZEBOX:
3079 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);
3080 break;
3081
3082 case WWT_CLOSEBOX:
3083 DrawCloseBox(r, this->colour);
3084 break;
3085
3086 case WWT_DROPDOWN:
3087 if (this->index >= 0) w->SetStringParameters(this->index);
3088 DrawButtonDropdown(r, this->colour, false, clicked, this->GetString(), this->align);
3089 break;
3090
3092 case NWID_PUSHBUTTON_DROPDOWN:
3093 if (this->index >= 0) w->SetStringParameters(this->index);
3094 DrawButtonDropdown(r, this->colour, clicked, (this->disp_flags & ND_DROPDOWN_ACTIVE) != 0, this->GetString(), this->align);
3095 break;
3096
3097 default:
3098 NOT_REACHED();
3099 }
3100 if (this->index >= 0) w->DrawWidget(r, this->index);
3101
3102 if (this->IsDisabled()) {
3104 }
3105
3106 DrawOutline(w, this);
3107}
3108
3117{
3118 if (_current_text_dir == TD_LTR) {
3119 int button_width = this->pos_x + this->current_x - NWidgetLeaf::dropdown_dimension.width;
3120 return pt.x < button_width;
3121 } else {
3122 int button_left = this->pos_x + NWidgetLeaf::dropdown_dimension.width;
3123 return pt.x >= button_left;
3124 }
3125}
3126
3127/* == Conversion code from NWidgetPart array to NWidgetBase* tree == */
3128
3135{
3136 return tp > WPT_ATTRIBUTE_BEGIN && tp < WPT_ATTRIBUTE_END;
3137}
3138
3146{
3147 switch (nwid.type) {
3148 case WPT_RESIZE: {
3149 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3150 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_RESIZE requires NWidgetResizeBase");
3151 assert(nwid.u.xy.x >= 0 && nwid.u.xy.y >= 0);
3152 nwrb->SetResize(nwid.u.xy.x, nwid.u.xy.y);
3153 break;
3154 }
3155
3156 case WPT_MINSIZE: {
3157 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3158 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_MINSIZE requires NWidgetResizeBase");
3159 assert(nwid.u.xy.x >= 0 && nwid.u.xy.y >= 0);
3160 nwrb->SetMinimalSize(nwid.u.xy.x, nwid.u.xy.y);
3161 break;
3162 }
3163
3164 case WPT_MINTEXTLINES: {
3165 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3166 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_MINTEXTLINES requires NWidgetResizeBase");
3167 assert(nwid.u.text_lines.size >= FS_BEGIN && nwid.u.text_lines.size < FS_END);
3169 break;
3170 }
3171
3172 case WPT_TEXTSTYLE: {
3173 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3174 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_TEXTSTYLE requires NWidgetCore");
3175 nwc->SetTextStyle(nwid.u.text_style.colour, nwid.u.text_style.size);
3176 break;
3177 }
3178
3179 case WPT_ALIGNMENT: {
3180 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3181 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_ALIGNMENT requires NWidgetCore");
3182 nwc->SetAlignment(nwid.u.align.align);
3183 break;
3184 }
3185
3186 case WPT_FILL: {
3187 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3188 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_FILL requires NWidgetResizeBase");
3189 nwrb->SetFill(nwid.u.xy.x, nwid.u.xy.y);
3190 break;
3191 }
3192
3193 case WPT_DATATIP: {
3194 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3195 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_DATATIP requires NWidgetCore");
3196 nwc->widget_data = nwid.u.data_tip.data;
3197 nwc->SetToolTip(nwid.u.data_tip.tooltip);
3198 break;
3199 }
3200
3201 case WPT_PADDING:
3202 if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_PADDING requires NWidgetBase");
3203 dest->SetPadding(nwid.u.padding);
3204 break;
3205
3206 case WPT_PIPSPACE: {
3207 NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(dest);
3208 if (nwc != nullptr) nwc->SetPIP(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3209
3210 NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(dest);
3211 if (nwb != nullptr) nwb->SetPIP(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3212
3213 if (nwc == nullptr && nwb == nullptr) [[unlikely]] throw std::runtime_error("WPT_PIPSPACE requires NWidgetPIPContainer or NWidgetBackground");
3214 break;
3215 }
3216
3217 case WPT_PIPRATIO: {
3218 NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(dest);
3219 if (nwc != nullptr) nwc->SetPIPRatio(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3220
3221 NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(dest);
3222 if (nwb != nullptr) nwb->SetPIPRatio(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3223
3224 if (nwc == nullptr && nwb == nullptr) [[unlikely]] throw std::runtime_error("WPT_PIPRATIO requires NWidgetPIPContainer or NWidgetBackground");
3225 break;
3226 }
3227
3228 case WPT_SCROLLBAR: {
3229 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3230 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_SCROLLBAR requires NWidgetCore");
3231 nwc->scrollbar_index = nwid.u.widget.index;
3232 break;
3233 }
3234
3235 case WPT_ASPECT: {
3236 if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_ASPECT requires NWidgetBase");
3237 dest->aspect_ratio = nwid.u.aspect.ratio;
3238 dest->aspect_flags = nwid.u.aspect.flags;
3239 break;
3240 }
3241
3242 default:
3243 NOT_REACHED();
3244 }
3245}
3246
3253static std::unique_ptr<NWidgetBase> MakeNWidget(const NWidgetPart &nwid)
3254{
3255 assert(!IsAttributeWidgetPartType(nwid.type));
3256 assert(nwid.type != WPT_ENDCONTAINER);
3257
3258 switch (nwid.type) {
3259 case NWID_SPACER: return std::make_unique<NWidgetSpacer>(0, 0);
3260
3261 case WWT_PANEL: [[fallthrough]];
3262 case WWT_INSET: [[fallthrough]];
3263 case WWT_FRAME: return std::make_unique<NWidgetBackground>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index);
3264
3265 case NWID_HORIZONTAL: return std::make_unique<NWidgetHorizontal>(nwid.u.cont_flags);
3266 case NWID_HORIZONTAL_LTR: return std::make_unique<NWidgetHorizontalLTR>(nwid.u.cont_flags);
3267 case NWID_VERTICAL: return std::make_unique<NWidgetVertical>(nwid.u.cont_flags);
3268 case NWID_SELECTION: return std::make_unique<NWidgetStacked>(nwid.u.widget.index);
3269 case NWID_MATRIX: return std::make_unique<NWidgetMatrix>(nwid.u.widget.colour, nwid.u.widget.index);
3270 case NWID_VIEWPORT: return std::make_unique<NWidgetViewport>(nwid.u.widget.index);
3271 case NWID_LAYER: return std::make_unique<NWidgetLayer>(nwid.u.widget.index);
3272
3273 case NWID_HSCROLLBAR: [[fallthrough]];
3274 case NWID_VSCROLLBAR: return std::make_unique<NWidgetScrollbar>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index);
3275
3276 case WPT_FUNCTION: return nwid.u.func_ptr();
3277
3278 default:
3279 assert((nwid.type & WWT_MASK) < WWT_LAST || (nwid.type & WWT_MASK) == NWID_BUTTON_DROPDOWN);
3280 return std::make_unique<NWidgetLeaf>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index, WidgetData{}, STR_NULL);
3281 }
3282}
3283
3297static 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)
3298{
3299 dest = nullptr;
3300
3301 if (IsAttributeWidgetPartType(nwid_begin->type)) [[unlikely]] throw std::runtime_error("Expected non-attribute NWidgetPart type");
3302 if (nwid_begin->type == WPT_ENDCONTAINER) return nwid_begin;
3303
3304 fill_dest = IsContainerWidgetType(nwid_begin->type);
3305 dest = MakeNWidget(*nwid_begin);
3306 if (dest == nullptr) return nwid_begin;
3307
3308 ++nwid_begin;
3309
3310 /* Once a widget is created, we're now looking for attributes. */
3311 while (nwid_begin != nwid_end && IsAttributeWidgetPartType(nwid_begin->type)) {
3312 ApplyNWidgetPartAttribute(*nwid_begin, dest.get());
3313 ++nwid_begin;
3314 }
3315
3316 return nwid_begin;
3317}
3318
3325{
3326 return tp == NWID_HORIZONTAL || tp == NWID_HORIZONTAL_LTR || tp == NWID_VERTICAL || tp == NWID_MATRIX
3327 || tp == WWT_PANEL || tp == WWT_FRAME || tp == WWT_INSET || tp == NWID_SELECTION || tp == NWID_LAYER;
3328}
3329
3337static 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)
3338{
3339 /* If *parent == nullptr, only the first widget is read and returned. Otherwise, *parent must point to either
3340 * a #NWidgetContainer or a #NWidgetBackground object, and parts are added as much as possible. */
3341 NWidgetContainer *nwid_cont = dynamic_cast<NWidgetContainer *>(parent.get());
3342 NWidgetBackground *nwid_parent = dynamic_cast<NWidgetBackground *>(parent.get());
3343 assert(parent == nullptr || (nwid_cont != nullptr && nwid_parent == nullptr) || (nwid_cont == nullptr && nwid_parent != nullptr));
3344
3345 while (nwid_begin != nwid_end) {
3346 std::unique_ptr<NWidgetBase> sub_widget = nullptr;
3347 bool fill_sub = false;
3348 nwid_begin = MakeNWidget(nwid_begin, nwid_end, sub_widget, fill_sub);
3349
3350 /* Break out of loop when end reached */
3351 if (sub_widget == nullptr) break;
3352
3353 /* If sub-widget is a container, recursively fill that container. */
3354 if (fill_sub && IsContainerWidgetType(sub_widget->type)) {
3355 nwid_begin = MakeWidgetTree(nwid_begin, nwid_end, sub_widget);
3356 }
3357
3358 /* Add sub_widget to parent container if available, otherwise return the widget to the caller. */
3359 if (nwid_cont != nullptr) nwid_cont->Add(std::move(sub_widget));
3360 if (nwid_parent != nullptr) nwid_parent->Add(std::move(sub_widget));
3361 if (nwid_cont == nullptr && nwid_parent == nullptr) {
3362 parent = std::move(sub_widget);
3363 return nwid_begin;
3364 }
3365 }
3366
3367 if (nwid_begin == nwid_end) return nwid_begin; // Reached the end of the array of parts?
3368
3369 assert(nwid_begin < nwid_end);
3370 assert(nwid_begin->type == WPT_ENDCONTAINER);
3371 return std::next(nwid_begin); // *nwid_begin is also 'used'
3372}
3373
3381std::unique_ptr<NWidgetBase> MakeNWidgets(std::span<const NWidgetPart> nwid_parts, std::unique_ptr<NWidgetBase> &&container)
3382{
3383 if (container == nullptr) container = std::make_unique<NWidgetVertical>();
3384 [[maybe_unused]] auto nwid_part = MakeWidgetTree(std::begin(nwid_parts), std::end(nwid_parts), container);
3385#ifdef WITH_ASSERT
3386 if (nwid_part != std::end(nwid_parts)) [[unlikely]] throw std::runtime_error("Did not consume all NWidgetParts");
3387#endif
3388 return std::move(container);
3389}
3390
3400std::unique_ptr<NWidgetBase> MakeWindowNWidgetTree(std::span<const NWidgetPart> nwid_parts, NWidgetStacked **shade_select)
3401{
3402 auto nwid_begin = std::begin(nwid_parts);
3403 auto nwid_end = std::end(nwid_parts);
3404
3405 *shade_select = nullptr;
3406
3407 /* Read the first widget recursively from the array. */
3408 std::unique_ptr<NWidgetBase> nwid = nullptr;
3409 nwid_begin = MakeWidgetTree(nwid_begin, nwid_end, nwid);
3410 assert(nwid != nullptr);
3411
3412 NWidgetHorizontal *hor_cont = dynamic_cast<NWidgetHorizontal *>(nwid.get());
3413
3414 auto root = std::make_unique<NWidgetVertical>();
3415 root->Add(std::move(nwid));
3416 if (nwid_begin == nwid_end) return root; // There is no body at all.
3417
3418 if (hor_cont != nullptr && hor_cont->GetWidgetOfType(WWT_CAPTION) != nullptr && hor_cont->GetWidgetOfType(WWT_SHADEBOX) != nullptr) {
3419 /* If the first widget has a title bar and a shade box, silently add a shade selection widget in the tree. */
3420 auto shade_stack = std::make_unique<NWidgetStacked>(-1);
3421 *shade_select = shade_stack.get();
3422 /* Load the remaining parts into the shade stack. */
3423 shade_stack->Add(MakeNWidgets({nwid_begin, nwid_end}, std::make_unique<NWidgetVertical>()));
3424 root->Add(std::move(shade_stack));
3425 return root;
3426 }
3427
3428 /* Load the remaining parts into 'root'. */
3429 return MakeNWidgets({nwid_begin, nwid_end}, std::move(root));
3430}
3431
3442std::unique_ptr<NWidgetBase> MakeCompanyButtonRows(WidgetID widget_first, WidgetID widget_last, Colours button_colour, int max_length, StringID button_tooltip, bool resizable)
3443{
3444 assert(max_length >= 1);
3445 std::unique_ptr<NWidgetVertical> vert = nullptr; // Storage for all rows.
3446 std::unique_ptr<NWidgetHorizontal> hor = nullptr; // Storage for buttons in one row.
3447 int hor_length = 0;
3448
3449 Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON, nullptr, ZOOM_LVL_NORMAL);
3450 sprite_size.width += WidgetDimensions::unscaled.matrix.Horizontal();
3451 sprite_size.height += WidgetDimensions::unscaled.matrix.Vertical();
3452
3453 for (WidgetID widnum = widget_first; widnum <= widget_last; widnum++) {
3454 /* Ensure there is room in 'hor' for another button. */
3455 if (hor_length == max_length) {
3456 if (vert == nullptr) vert = std::make_unique<NWidgetVertical>();
3457 vert->Add(std::move(hor));
3458 hor = nullptr;
3459 hor_length = 0;
3460 }
3461 if (hor == nullptr) {
3462 hor = std::make_unique<NWidgetHorizontal>();
3463 hor_length = 0;
3464 }
3465
3466 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, button_colour, widnum);
3467 panel->SetMinimalSize(sprite_size.width, sprite_size.height);
3468 panel->SetFill(1, 1);
3469 if (resizable) panel->SetResize(1, 0);
3470 panel->SetToolTip(button_tooltip);
3471 hor->Add(std::move(panel));
3472 hor_length++;
3473 }
3474 if (vert == nullptr) return hor; // All buttons fit in a single row.
3475
3476 if (hor_length > 0 && hor_length < max_length) {
3477 /* Last row is partial, add a spacer at the end to force all buttons to the left. */
3478 auto spc = std::make_unique<NWidgetSpacer>(sprite_size.width, sprite_size.height);
3479 spc->SetFill(1, 1);
3480 if (resizable) spc->SetResize(1, 0);
3481 hor->Add(std::move(spc));
3482 }
3483 if (hor != nullptr) vert->Add(std::move(hor));
3484 return vert;
3485}
Class for backupping variables and making sure they are restored later.
debug_inline constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
constexpr T SetBit(T &x, const uint8_t y)
Set a bit in a variable.
Enum-as-bit-set wrapper.
constexpr bool Test(Tenum value) const
Test if the enum value is set.
Nested widget with a child.
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:2371
NWidgetBase * GetWidgetOfType(WidgetType tp) override
Retrieve a widget by its type.
Definition widget.cpp:2381
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:2315
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2250
NWidgetBackground(WidgetType tp, Colours colour, WidgetID index, std::unique_ptr< NWidgetPIPContainer > &&child=nullptr)
Constructor parent nested widgets.
Definition widget.cpp:2182
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:2216
std::unique_ptr< NWidgetPIPContainer > child
Child widget.
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2333
void Add(std::unique_ptr< NWidgetBase > &&nwid)
Add a child to the parent.
Definition widget.cpp:2197
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:2327
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:2235
Baseclass for nested widgets.
void StoreSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height)
Store size and position.
float aspect_ratio
Desired aspect ratio of widget.
virtual void SetupSmallestSize(Window *w)=0
Compute smallest size needed by the widget.
virtual void SetDirty(const Window *w) const
Mark the widget as 'dirty' (in need of repaint).
Definition widget.cpp:903
WidgetType type
Type of the widget / nested widget.
uint resize_x
Horizontal resize step (0 means not resizable).
uint fill_x
Horizontal fill stepsize (from initial size, 0 means not resizable).
NWidgetBase * parent
Parent widget of this widget, automatically filled in when added to container.
virtual void FillWidgetLookup(WidgetLookup &widget_lookup)=0
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
RectPadding uz_padding
Unscaled padding, for resize calculation.
uint smallest_x
Smallest horizontal size of the widget in a filled window.
AspectFlags aspect_flags
Which dimensions can be resized.
virtual void Draw(const Window *w)=0
Draw the widgets of the tree.
uint current_x
Current horizontal size (after resizing).
int pos_y
Vertical position of top-left corner of the widget in the window.
int pos_x
Horizontal position of top-left corner of the widget in the window.
void SetPadding(uint8_t top, uint8_t right, uint8_t bottom, uint8_t left)
Set additional space (padding) around the widget.
uint smallest_y
Smallest vertical size of the widget in a filled window.
virtual NWidgetBase * GetWidgetOfType(WidgetType tp)
Retrieve a widget by its type.
Definition widget.cpp:923
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 ...
NWidgetBase(WidgetType tp)
Base class constructor.
Definition widget.cpp:851
uint current_y
Current vertical size (after resizing).
Baseclass for container widgets.
void Add(std::unique_ptr< NWidgetBase > &&wid)
Append widget wid to container.
Definition widget.cpp:1281
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1288
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1295
bool IsEmpty()
Return whether the container is empty.
std::vector< std::unique_ptr< NWidgetBase > > children
Child widgets in contaier.
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1304
NWidgetBase * GetWidgetOfType(WidgetType tp) override
Retrieve a widget by its type.
Definition widget.cpp:1259
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:1199
bool IsDisabled() const
Return whether the widget is disabled.
NWidgetDisplay disp_flags
Flags that affect display and interaction with the widget.
void SetSprite(SpriteID sprite)
Set sprite of the nested widget.
Definition widget.cpp:1149
WidgetID GetIndex() const
Get the WidgetID of this nested widget.
Definition widget.cpp:1235
void SetTextStyle(TextColour colour, FontSize size)
Set the text style of the nested widget.
Definition widget.cpp:1189
WidgetID GetScrollbarIndex() const
Get the WidgetID of this nested widget's scrollbar.
Definition widget.cpp:1244
void SetAlignment(StringAlignment align)
Set the text/image alignment of the nested widget.
Definition widget.cpp:1217
void SetResizeWidgetType(ResizeWidgetValues type)
Set the resize widget type of the nested widget.
Definition widget.cpp:1179
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:1114
void SetSpriteTip(SpriteID sprite, StringID tool_tip)
Set sprite and tool tip of the nested widget.
Definition widget.cpp:1159
const WidgetID index
Index of the nested widget (-1 means 'not used').
StringAlignment align
Alignment of text/image within widget.
FontSize text_size
Size of text within widget.
StringID GetString() const
Get the string that has been set for this nested widget.
Definition widget.cpp:1226
WidgetID scrollbar_index
Index of an attached scrollbar.
StringID GetToolTip() const
Get the tool tip of the nested widget.
Definition widget.cpp:1208
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1254
void SetString(StringID string)
Set string of the nested widget.
Definition widget.cpp:1129
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:1170
void SetLowered(bool lowered)
Lower or raise the widget.
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1249
void SetStringTip(StringID string, StringID tool_tip)
Set string and tool tip of the nested widget.
Definition widget.cpp:1139
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:1729
NWidgetHorizontalLTR(NWidContainerFlags flags=NC_NONE)
Horizontal left-to-right container widget.
Definition widget.cpp:1724
Horizontal container.
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1545
NWidgetHorizontal(NWidContainerFlags flags=NC_NONE)
Horizontal container widget.
Definition widget.cpp:1541
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:1614
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
const WidgetID index
If non-negative, index in the Window::widget_lookup.
Definition widget.cpp:1436
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:2980
static void InvalidateDimensionCache()
Reset the cached dimensions.
Definition widget.cpp:2676
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:2703
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:2809
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:3116
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2096
int count
Amount of valid elements.
void SetClicked(int clicked)
Sets the clicked element in the matrix.
Definition widget.cpp:1959
int GetCurrentElement() const
Get current element.
Definition widget.cpp:2009
const WidgetID index
If non-negative, index in the Window::widget_lookup.
Scrollbar * sb
The scrollbar we're associated with.
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2014
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:2000
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:2151
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:2036
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:2060
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:2066
int clicked
The currently clicked element.
void SetCount(int count)
Set the number of elements in this matrix.
Definition widget.cpp:1976
int widgets_y
The number of visible widgets in vertical direction.
Colours colour
Colour of this widget.
Container with pre/inter/post child space.
uint8_t gaps
Number of gaps between widgets.
NWidContainerFlags flags
Flags of the container.
uint8_t pip_pre
Amount of space before first widget.
uint8_t uz_pip_pre
Unscaled space before first widget.
uint8_t uz_pip_inter
Unscaled space between widgets.
uint8_t pip_ratio_pre
Ratio of remaining space before first widget.
void SetPIPRatio(uint8_t pip_ratio_pre, uint8_t pip_ratio_inter, uint8_t pip_rato_post)
Set additional pre/inter/post space for the container.
Definition widget.cpp:1533
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:1513
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:995
bool UpdateSize(uint min_x, uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1078
uint min_x
Minimal horizontal size of only this widget.
FontSize uz_text_size
'Unscaled' font size, stored for resize calculation.
NWidgetResizeBase(WidgetType tp, uint fill_x, uint fill_y)
Constructor for resizable nested widgets.
Definition widget.cpp:953
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:1034
void SetMinimalTextLines(uint8_t min_lines, uint8_t spacing, FontSize size)
Set minimal text lines for the widget.
Definition widget.cpp:1021
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:1058
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:1099
bool UpdateVerticalSize(uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1092
void SetResize(uint resize_x, uint resize_y)
Set resize step of the widget.
Definition widget.cpp:1045
void SetAspect(float ratio, AspectFlags flags=AspectFlag::ResizeX)
Set desired aspect ratio of this widget.
Definition widget.cpp:964
void SetMinimalSizeAbsolute(uint min_x, uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1008
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:2620
NWidgetScrollbar(WidgetType tp, Colours colour, WidgetID index)
Scrollbar widget.
Definition widget.cpp:2578
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2599
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:1911
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1924
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1945
void SetDirty(const Window *w) const override
Mark the widget as 'dirty' (in need of repaint).
Definition widget.cpp:1940
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1928
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1917
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:1322
const WidgetID index
If non-negative, index in the Window::widget_lookup.
WidgetLookup * widget_lookup
Window's widget lookup, updated in SetDisplayedPlane().
void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override
Assign size and position to the widget.
Definition widget.cpp:1363
NWidgetStacked(WidgetID index)
Widgets stacked on top of each other.
Definition widget.cpp:1318
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1394
bool SetDisplayedPlane(int plane)
Select which plane to show (for NWID_SELECTION only).
Definition widget.cpp:1418
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1383
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1403
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:1808
NWidgetVertical(NWidContainerFlags flags=NC_NONE)
Vertical container widget.
Definition widget.cpp:1735
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1739
void UpdateViewportCoordinates(Window *w)
Update the position and size of the viewport (after eg a resize).
Definition widget.cpp:2436
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2400
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2393
void InitializeViewport(Window *w, std::variant< TileIndex, VehicleID > focus, ZoomLevel zoom)
Initialize the viewport of the window.
Definition widget.cpp:2427
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:2459
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:2533
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:2480
void ScrollTowards(size_type position)
Scroll towards the given position; if the item is visible nothing happens, otherwise it will be shown...
void SetStepSize(size_t stepsize)
Set the distance to scroll when using the buttons or the wheel.
size_type pos
Index of first visible item of the list.
size_type GetPosition() const
Gets the position of the first visible element in the list.
@ SS_BIG
Step in cap units.
static constexpr uint WD_CAPTION_HEIGHT
Minimum height of a title bar.
Definition window_gui.h:88
RectPadding defsizebox
Padding around image in defsizebox widget.
Definition window_gui.h:46
RectPadding closebox
Padding around image in closebox widget.
Definition window_gui.h:48
RectPadding framerect
Standard padding inside many panels.
Definition window_gui.h:40
static constexpr uint WD_DROPDOWN_HEIGHT
Minimum height of a drop down widget.
Definition window_gui.h:89
RectPadding captiontext
Padding for text within caption widget.
Definition window_gui.h:49
RectPadding debugbox
Padding around image in debugbox widget.
Definition window_gui.h:45
RectPadding frametext
Padding inside frame with text.
Definition window_gui.h:41
static WidgetDimensions scaled
Widget dimensions scaled for current zoom level.
Definition window_gui.h:28
RectPadding modalpopup
Spacing for popup warning/information windows.
Definition window_gui.h:52
RectPadding hscrollbar
Padding inside horizontal scrollbar buttons.
Definition window_gui.h:37
RectPadding shadebox
Padding around image in shadebox widget.
Definition window_gui.h:43
RectPadding resizebox
Padding around image in resizebox widget.
Definition window_gui.h:47
RectPadding vscrollbar
Padding inside vertical scrollbar buttons.
Definition window_gui.h:36
RectPadding imgbtn
Padding around image button image.
Definition window_gui.h:34
int vsep_normal
Normal vertical spacing.
Definition window_gui.h:58
int vsep_wide
Wide vertical spacing.
Definition window_gui.h:60
int hsep_wide
Wide horizontal spacing.
Definition window_gui.h:62
static constexpr uint WD_CLOSEBOX_WIDTH
Minimum width of a close box widget.
Definition window_gui.h:87
RectPadding fullbevel
Always-scaled bevel thickness.
Definition window_gui.h:39
RectPadding inset
Padding inside inset container.
Definition window_gui.h:35
static const WidgetDimensions unscaled
Unscaled widget dimensions.
Definition window_gui.h:94
static constexpr uint WD_RESIZEBOX_WIDTH
Minimum width of a resize box widget.
Definition window_gui.h:86
static constexpr uint WD_STICKYBOX_WIDTH
Minimum width of a standard sticky box widget.
Definition window_gui.h:83
RectPadding matrix
Padding of WWT_MATRIX items.
Definition window_gui.h:42
int hsep_normal
Normal horizontal spacing.
Definition window_gui.h:61
RectPadding dropdownlist
Padding of complete drop down list.
Definition window_gui.h:51
static constexpr uint WD_DEBUGBOX_WIDTH
Minimum width of a standard debug box widget.
Definition window_gui.h:84
RectPadding stickybox
Padding around image in stickybox widget.
Definition window_gui.h:44
static constexpr uint WD_DEFSIZEBOX_WIDTH
Minimum width of a standard defsize box widget.
Definition window_gui.h:85
RectPadding bevel
Bevel thickness, affected by "scaled bevels" game option.
Definition window_gui.h:38
RectPadding dropdowntext
Padding of drop down list item.
Definition window_gui.h:50
static constexpr uint WD_SHADEBOX_WIDTH
Distances used in drawing widgets.
Definition window_gui.h:82
int hsep_indent
Width of identation for tree layouts.
Definition window_gui.h:63
Base class that provides memory initialization on dynamically created objects.
Colours _company_colours[MAX_COMPANIES]
NOSAVE: can be determined from company structs.
Functions related to companies.
Owner
Enum for all companies/owners.
@ MAX_COMPANIES
Maximum number of companies.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:77
Dimension maxdim(const Dimension &d1, const Dimension &d2)
Compute bounding box of both dimensions.
Geometry functions.
int GetStringHeight(std::string_view str, int maxw, FontSize fontsize)
Calculates height of string (in pixels).
Definition gfx.cpp:704
Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
Get the size of a sprite.
Definition gfx.cpp:922
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:851
void DrawRectOutline(const Rect &r, int colour, int width, int dash)
Draw the outline of a Rect.
Definition gfx.cpp:456
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:657
void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
Definition gfx.cpp:114
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:988
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:1548
Dimension GetScaledSpriteSize(SpriteID sprid)
Scale sprite size for GUI.
Definition widget.cpp:54
int CenterBounds(int min, int max, int size)
Determine where to draw a centred object inside a widget.
Definition gfx_func.h:166
uint32_t SpriteID
The number of a sprite, without mapping bits and colourtables.
Definition gfx_type.h:17
FontSize
Available font sizes.
Definition gfx_type.h:242
@ FS_BEGIN
First font.
Definition gfx_type.h:249
@ FS_NORMAL
Index of the normal font in the font tables.
Definition gfx_type.h:243
StringAlignment
How to align the to-be drawn text.
Definition gfx_type.h:374
@ SA_TOP
Top align the text.
Definition gfx_type.h:380
@ SA_LEFT
Left align the text.
Definition gfx_type.h:375
@ SA_HOR_MASK
Mask for horizontal alignment.
Definition gfx_type.h:378
@ SA_RIGHT
Right align the text (must be a single bit).
Definition gfx_type.h:377
@ SA_HOR_CENTER
Horizontally center the text.
Definition gfx_type.h:376
@ SA_VERT_MASK
Mask for vertical alignment.
Definition gfx_type.h:383
@ SA_FORCE
Force the alignment, i.e. don't swap for RTL languages.
Definition gfx_type.h:387
@ SA_BOTTOM
Bottom align the text.
Definition gfx_type.h:382
@ SA_CENTER
Center both horizontally and vertically.
Definition gfx_type.h:385
@ SA_VERT_CENTER
Vertically center the text.
Definition gfx_type.h:381
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:294
@ FILLRECT_CHECKER
Draw only every second pixel, used for greying-out.
Definition gfx_type.h:333
@ FILLRECT_RECOLOUR
Apply a recolour sprite to the screen content.
Definition gfx_type.h:334
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:3400
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:3381
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:937
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:1486
constexpr bool IsInsideBS(const T x, const size_t base, const size_t size)
Checks if a value is between a window started at some base point.
constexpr uint CeilDiv(uint a, uint b)
Computes ceil(a / b) for non-negative a and b.
constexpr T Clamp(const T a, const T min, const T max)
Clamp a value between an interval.
Definition math_func.hpp:79
uint8_t GetColourGradient(Colours colour, ColourShade shade)
Get colour gradient palette index.
Definition palette.cpp:387
static const uint8_t PC_WHITE
White palette colour.
static const uint8_t PC_BLACK
Black palette colour.
Base for the GUIs that have an edit box in them.
A number of safeguards to prevent using unsafe methods.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:56
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:1532
static const PaletteID PALETTE_TO_TRANSPARENT
This sets the sprite to transparent.
Definition sprites.h:1602
static const PaletteID PALETTE_NEWSPAPER
Recolour sprite for newspaper-greying.
Definition sprites.h:1604
Definition of base types and functions in a cross-platform compatible way.
The colour translation of GRF's strings.
static const uint8_t _string_colourmap[17]
Colour mapping for TextColour.
TextDirection _current_text_dir
Text direction of the currently selected language.
Definition strings.cpp:56
Functions related to OTTD's strings.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
@ TD_LTR
Text is written left-to-right by default.
@ TD_RTL
Text is written right-to-left by default.
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
GUISettings gui
settings related to the GUI
Point pos
logical mouse position
Definition gfx_type.h:124
Dimensions (a width and height) of a rectangle in 2D.
Data about how and where to blit pixels.
Definition gfx_type.h:156
bool scale_bevels
bevels are scaled with GUI scale.
bool newgrf_developer_tools
activate NewGRF developer tools and allow modifying NewGRFs in an existing game
StringAlignment align
Alignment of text/image.
WidgetData data
Data value of the widget.
StringID tooltip
Tooltip of the widget.
uint8_t post
Amount of space before/between/after child widgets.
uint8_t lines
Number of text lines.
uint8_t spacing
Extra spacing around lines.
FontSize size
Font size of text lines.
TextColour colour
TextColour for DrawString.
FontSize size
Font size of text.
Colours colour
Widget colour.
WidgetID index
Index of the widget.
Partial widget specification to allow NWidgets to be written nested.
WidgetType type
Type of the part.
Coordinates of a point in 2D.
Data stored about a string that can be modified in the GUI.
Padding dimensions to apply to each side of a Rect.
constexpr uint Horizontal() const
Get total horizontal padding of RectPadding.
constexpr uint Vertical() const
Get total vertical padding of RectPadding.
Specification of a rectangle with absolute coordinates of all edges.
Rect WithWidth(int width, bool end) const
Copy Rect and set its width.
int Width() const
Get width of Rect.
Rect Shrink(int s) const
Copy and shrink Rect by s pixels.
Rect WithHeight(int height, bool end=false) const
Copy Rect and set its height.
int Height() const
Get height of Rect.
Rect Expand(int s) const
Copy and expand Rect by s pixels.
Data structure for viewport, display of a part of the world.
int top
Screen coordinate top edge of the viewport.
int width
Screen width of the viewport.
ZoomLevel zoom
The zoom level of the viewport.
int virtual_width
width << zoom
int left
Screen coordinate left edge of the viewport.
int height
Screen height of the viewport.
int virtual_height
height << zoom
Container with the data associated to a single widget.
Data structure for an opened window.
Definition window_gui.h:272
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:617
static int SortButtonWidth()
Get width of up/down arrow of sort button state.
Definition widget.cpp:780
void DrawWidgets() const
Paint all widgets of a window.
Definition widget.cpp:731
virtual void SetStringParameters(WidgetID widget) const
Initialize string parameters for a widget.
Definition window_gui.h:625
WidgetID mouse_capture_widget
ID of current mouse capture widget (e.g. dragged scrollbar). -1 if no widget has mouse capture.
Definition window_gui.h:325
void DrawSortButtonState(WidgetID widget, SortButtonState state) const
Draw a sort button's up or down arrow symbol.
Definition widget.cpp:763
virtual bool IsNewGRFInspectable() const
Is the data related to this window NewGRF inspectable?
Definition window_gui.h:846
ViewportData * viewport
Pointer to viewport data, if present.
Definition window_gui.h:317
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:603
Owner owner
The owner of the content shown in this window. Company colour is acquired from this variable.
Definition window_gui.h:315
int left
x position of left edge of the window
Definition window_gui.h:308
bool IsShaded() const
Is window shaded currently?
Definition window_gui.h:556
int top
y position of top edge of the window
Definition window_gui.h:309
const QueryString * GetQueryString(WidgetID widnum) const
Return the querystring associated to a editbox.
Definition window.cpp:331
WidgetLookup widget_lookup
Indexed access to the nested widget tree. Do not access directly, use Window::GetWidget() instead.
Definition window_gui.h:321
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:210
const NWID * GetWidget(WidgetID widnum) const
Get the nested widget with number widnum from the nested widget tree.
Definition window_gui.h:970
WindowFlags flags
Window flags.
Definition window_gui.h:299
std::unique_ptr< NWidgetBase > nested_root
Root of the nested tree.
Definition window_gui.h:320
int height
Height of the window (number of pixels down in y direction)
Definition window_gui.h:311
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:310
Functions related to transparency.
TransparencyOptionBits _transparency_opt
The bits that should be transparent.
uint TransparencyOptionBits
transparency option bits
@ TO_TEXT
loading and cost/income text
@ TO_SIGNS
signs
NWidgetPartTextLines text_lines
Part with text line data.
NWidgetPartPIP pip
Part with pre/inter/post spaces.
NWidgetPartPaddings padding
Part with paddings.
NWidgetFunctionType * func_ptr
Part with a function call.
NWidContainerFlags cont_flags
Part with container flags.
NWidgetPartDataTip data_tip
Part with a data/tooltip.
NWidgetPartAlignment align
Part with internal alignment.
NWidgetPartAspect aspect
Part to set aspect ratio.
NWidgetPartTextStyle text_style
Part with text style data.
Point xy
Part with an x/y size.
NWidgetPartWidget widget
Part with a start of a widget.
void InitializeWindowViewport(Window *w, int x, int y, int width, int height, std::variant< TileIndex, VehicleID > focus, ZoomLevel zoom)
Initialize viewport of the window for use.
Definition viewport.cpp:222
Functions related to (drawing on) viewports.
static void DrawCloseBox(const Rect &r, Colours colour)
Draw a close box.
Definition widget.cpp:658
static void DrawButtonDropdown(const Rect &r, Colours colour, bool clicked_button, bool clicked_dropdown, StringID str, StringAlignment align)
Draw a button with a dropdown (WWT_DROPDOWN and NWID_BUTTON_DROPDOWN).
Definition widget.cpp:709
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:283
Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
Apply 'scroll' to a rect to be drawn in.
Definition widget.cpp:2550
void ApplyNWidgetPartAttribute(const NWidgetPart &nwid, NWidgetBase *dest)
Apply an attribute NWidgetPart to an NWidget.
Definition widget.cpp:3145
static void DrawDefSizeBox(const Rect &r, Colours colour, bool clicked)
Draw a defsize box.
Definition widget.cpp:619
bool IsContainerWidgetType(WidgetType tp)
Test if WidgetType is a container widget.
Definition widget.cpp:3324
static void DrawDebugBox(const Rect &r, Colours colour, bool clicked)
Draw a NewGRF debug box.
Definition widget.cpp:630
static void DrawStickyBox(const Rect &r, Colours colour, bool clicked)
Draw a sticky box.
Definition widget.cpp:608
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:106
void DrawCaption(const Rect &r, Colours colour, Owner owner, TextColour text_colour, StringID str, StringAlignment align, FontSize fs)
Draw a caption bar.
Definition widget.cpp:679
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:406
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:504
Dimension GetScaledSpriteSize(SpriteID sprid)
Scale sprite size for GUI.
Definition widget.cpp:54
static std::unique_ptr< NWidgetBase > MakeNWidget(const NWidgetPart &nwid)
Make NWidget from an NWidgetPart.
Definition widget.cpp:3253
static void DrawLabel(const Rect &r, WidgetType type, bool clicked, TextColour colour, StringID str, StringAlignment align, FontSize fs)
Draw the label-part of a widget.
Definition widget.cpp:357
void SetupWidgetDimensions()
Set up pre-scaled versions of Widget Dimensions.
Definition widget.cpp:66
static void DrawShadeBox(const Rect &r, Colours colour, bool clicked)
Draw a shade box.
Definition widget.cpp:597
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:268
static void DrawImageButtons(const Rect &r, WidgetType type, Colours colour, bool clicked, SpriteID img, StringAlignment align)
Draw an image button.
Definition widget.cpp:338
static bool IsAttributeWidgetPartType(WidgetType tp)
Test if (an NWidgetPart) WidgetType is an attribute widget part type.
Definition widget.cpp:3134
static void DrawFrame(const Rect &r, Colours colour, TextColour text_colour, StringID str, StringAlignment align, FontSize fs)
Draw a frame widget.
Definition widget.cpp:544
static void DrawText(const Rect &r, TextColour colour, StringID str, StringAlignment align, FontSize fs)
Draw text.
Definition widget.cpp:374
void ScrollbarClickHandler(Window *w, NWidgetCore *nw, int x, int y)
Special handling for the scrollbar widget type.
Definition widget.cpp:244
static void DrawResizeBox(const Rect &r, Colours colour, bool at_left, bool clicked, bool bevel)
Draw a resize box.
Definition widget.cpp:643
static void DrawInset(const Rect &r, Colours colour, TextColour text_colour, StringID str, StringAlignment align, FontSize fs)
Draw an inset widget.
Definition widget.cpp:390
static Point HandleScrollbarHittest(const Scrollbar *sb, int top, int bottom, bool horizontal)
Compute the vertical position of the draggable part of scrollbar.
Definition widget.cpp:135
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:180
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:3337
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:464
static RectPadding ScaleGUITrad(const RectPadding &r)
Scale a RectPadding to GUI zoom level.
Definition widget.cpp:35
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:3442
@ ND_SHADE_GREY
Bit value of the 'shade to grey' flag.
@ ND_DROPDOWN_ACTIVE
Bit value of the 'dropdown active' flag.
@ NDB_SCROLLBAR_DOWN
Down-button is lowered bit.
@ ND_SHADE_DIMMED
Bit value of the 'dimmed colours' flag.
@ ND_NO_TRANSPARENCY
Bit value of the 'no transparency' flag.
@ NDB_SCROLLBAR_UP
Up-button is lowered bit.
@ ND_SCROLLBAR_BTN
Bit value of the 'scrollbar up' or 'scrollbar down' flag.
WidgetType
Window widget types, nested widget types, and nested widget part types.
Definition widget_type.h:37
@ 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:42
@ WPT_FILL
Widget part for specifying fill.
Definition widget_type.h:84
@ WPT_ALIGNMENT
Widget part for specifying text/image alignment.
Definition widget_type.h:90
@ WWT_IMGBTN
(Toggle) Button with image
Definition widget_type.h:43
@ 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:44
@ WWT_PUSHIMGBTN
Normal push-button (no toggle button) with image caption.
@ WPT_MINSIZE
Widget part for specifying minimal size.
Definition widget_type.h:82
@ WWT_PUSHARROWBTN
Normal push-button (no toggle button) with arrow caption.
@ WWT_LABEL
Centered label.
Definition widget_type.h:48
@ NWID_BUTTON_DROPDOWN
Button with a drop-down.
Definition widget_type.h:74
@ NWID_SPACER
Invisible widget that takes some space.
Definition widget_type.h:70
@ WWT_EDITBOX
a textbox for typing
Definition widget_type.h:62
@ WWT_ARROWBTN
(Toggle) Button with an arrow
Definition widget_type.h:45
@ NWID_HORIZONTAL
Horizontal container.
Definition widget_type.h:66
@ WWT_TEXTBTN
(Toggle) Button with text
Definition widget_type.h:46
@ WPT_SCROLLBAR
Widget part for attaching a scrollbar.
Definition widget_type.h:91
@ WWT_PANEL
Simple depressed panel.
Definition widget_type.h:41
@ WPT_ASPECT
Widget part for sepcifying aspect ratio.
Definition widget_type.h:92
@ WWT_STICKYBOX
Sticky box (at top-right of a window, after WWT_DEFSIZEBOX)
Definition widget_type.h:57
@ WPT_RESIZE
Widget part for specifying resizing.
Definition widget_type.h:81
@ WWT_MATRIX
Grid of rows and columns.
Definition widget_type.h:50
@ WWT_SHADEBOX
Shade box (at top-right of a window, between WWT_DEBUGBOX and WWT_DEFSIZEBOX)
Definition widget_type.h:55
@ WWT_CAPTION
Window caption (window title between closebox and stickybox)
Definition widget_type.h:52
@ NWID_VSCROLLBAR
Vertical scrollbar.
Definition widget_type.h:76
@ WPT_TEXTSTYLE
Widget part for specifying text colour.
Definition widget_type.h:89
@ WPT_PADDING
Widget part for specifying a padding.
Definition widget_type.h:86
@ NWID_VERTICAL
Vertical container.
Definition widget_type.h:68
@ WWT_CLOSEBOX
Close box (at top-left of a window)
Definition widget_type.h:60
@ WWT_TEXTBTN_2
(Toggle) Button with diff text when clicked
Definition widget_type.h:47
@ WWT_FRAME
Frame.
Definition widget_type.h:51
@ WPT_MINTEXTLINES
Widget part for specifying minimal number of lines of text.
Definition widget_type.h:83
@ WWT_EMPTY
Empty widget, place holder to reserve space in widget tree.
Definition widget_type.h:39
@ WWT_LAST
Last Item. use WIDGETS_END to fill up padding!!
Definition widget_type.h:63
@ WPT_ATTRIBUTE_END
End marker for attribute NWidgetPart types.
Definition widget_type.h:93
@ NWID_HSCROLLBAR
Horizontal scrollbar.
Definition widget_type.h:75
@ WPT_ENDCONTAINER
Widget part to denote end of a container.
Definition widget_type.h:96
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window)
Definition widget_type.h:59
@ WPT_PIPRATIO
Widget part for specifying pre/inter/post ratio for containers.
Definition widget_type.h:88
@ WPT_DATATIP
Widget part for specifying data and tooltip.
Definition widget_type.h:85
@ WWT_DEFSIZEBOX
Default window size box (at top-right of a window, between WWT_SHADEBOX and WWT_STICKYBOX)
Definition widget_type.h:56
@ WPT_ATTRIBUTE_BEGIN
Begin marker for attribute NWidgetPart types.
Definition widget_type.h:80
@ WPT_PIPSPACE
Widget part for specifying pre/inter/post space for containers.
Definition widget_type.h:87
@ NWID_MATRIX
Matrix container.
Definition widget_type.h:69
@ NWID_VIEWPORT
Nested widget containing a viewport.
Definition widget_type.h:73
@ WWT_DROPDOWN
Drop down list.
Definition widget_type.h:61
@ WWT_TEXT
Pure simple text.
Definition widget_type.h:49
@ NWID_HORIZONTAL_LTR
Horizontal container that doesn't change the order of the widgets for RTL languages.
Definition widget_type.h:67
@ WPT_FUNCTION
Widget part for calling a user function.
Definition widget_type.h:95
@ WWT_DEBUGBOX
NewGRF debug box (at top-right of a window, between WWT_CAPTION and WWT_SHADEBOX)
Definition widget_type.h:54
@ NWID_LAYER
Layered widgets, all visible together.
Definition widget_type.h:72
@ NWID_SELECTION
Stacked widgets, only one visible at a time (eg in a panel with tabs).
Definition widget_type.h:71
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.
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:24
@ AWV_RIGHT
Force the arrow to the right.
Definition widget_type.h:25
@ AWV_DECREASE
Arrow to the left or in case of RTL to the right.
Definition widget_type.h:22
@ AWV_INCREASE
Arrow to the right or in case of RTL to the left.
Definition widget_type.h:23
NWidContainerFlags
Nested widget container flags,.
@ NC_EQUALSIZE
Value of the NCB_EQUALSIZE flag.
@ NC_BIGFIRST
Value of the NCB_BIGFIRST flag.
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:29
@ RWV_SHOW_BEVEL
Bevel of resize box is shown.
Definition widget_type.h:30
bool _window_highlight_colour
If false, highlight is white, otherwise the by the widget defined colour.
Definition window.cpp:75
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:283
@ Transparent
Makes the background transparent if set.
@ BorderOnly
Draw border only, no background.
@ Darkened
If set the background is darker, allows for lowered frames with normal background colour when used wi...
@ Lowered
If set the frame is lowered and the background colour brighter (ie. buttons when pressed)
@ SizingLeft
Window is being resized towards the left.
@ Highlighted
Window has a widget that has a highlight.
@ SizingRight
Window is being resized towards the right.
@ WhiteBorder
Window white border counter bit mask.
@ Sticky
Window is made sticky by user.
SortButtonState
State of a sort direction button.
Definition window_gui.h:217
@ SBS_DOWN
Sort ascending.
Definition window_gui.h:219
@ SBS_OFF
Do not sort (with this button).
Definition window_gui.h:218
int WidgetID
Widget ID.
Definition window_type.h:20
EventState
State of handling an event.
@ ES_HANDLED
The passed event is handled.
@ ES_NOT_HANDLED
The passed event is not handled.
Functions related to zooming.
int ScaleByZoom(int value, ZoomLevel zoom)
Scale by zoom level, usually shift left (when zoom > ZOOM_LVL_MIN) When shifting right,...
Definition zoom_func.h:22
int ScaleSpriteTrad(int value)
Scale traditional pixel dimensions to GUI zoom level, for drawing sprites.
Definition zoom_func.h:107
ZoomLevel
All zoom levels we know.
Definition zoom_type.h:16
@ ZOOM_LVL_NORMAL
The normal zoom level.
Definition zoom_type.h:21