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