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