OpenTTD Source 20260711-master-g3fb3006dff
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
86{
88 uint x = std::max(d.width, d.height);
89 return {x, x};
90}
91
94
99{
104 if (_settings_client.gui.scale_bevels) {
106 } else {
108 }
123
129
131}
132
140static inline Point GetAlignedPosition(const Rect &r, const Dimension &d, Alignment align)
141{
142 Point p;
143 /* In case we have a RTL language we swap the alignment. */
144 switch (align.ResolveRTL()) {
145 case AlignmentH::ForceLeft: p.x = r.left; break;
146 case AlignmentH::Centre: p.x = CentreBounds(r.left, r.right, d.width); break;
147 case AlignmentH::ForceRight: p.x = r.right + 1 - d.width; break;
148 default: NOT_REACHED();
149 }
150 switch (align.v) {
151 case AlignmentV::Top: p.y = r.top; break;
152 case AlignmentV::Middle: p.y = CentreBounds(r.top, r.bottom, d.height); break;
153 case AlignmentV::Bottom: p.y = r.bottom + 1 - d.height; break;
154 default: NOT_REACHED();
155 }
156 return p;
157}
158
168static std::pair<int, int> HandleScrollbarHittest(const Scrollbar *sb, int mi, int ma, bool horizontal)
169{
170 /* Base for reversion */
171 int rev_base = mi + ma;
172 int button_size = horizontal ? NWidgetScrollbar::GetHorizontalDimension().width : NWidgetScrollbar::GetVerticalDimension().height;
173
174 mi += button_size; // now points to just after the up/left-button
175 ma -= button_size; // now points to just before the down/right-button
176
177 int count = sb->GetCount();
178 int cap = sb->GetCapacity();
179
180 if (count > cap) {
181 int height = ma + 1 - mi;
182 int slider_height = std::max(button_size, cap * height / count);
183 height -= slider_height;
184
185 mi += height * sb->GetPosition() / (count - cap);
186 ma = mi + slider_height - 1;
187 }
188
189 /* Reverse coordinates for RTL. */
190 if (horizontal && _current_text_dir == TD_RTL) return {rev_base - ma, rev_base - mi};
191
192 return {mi, ma};
193}
194
204static void ScrollbarClickPositioning(Window *w, NWidgetScrollbar *sb, int x, int y, int mi, int ma)
205{
206 int pos;
207 int button_size;
208 bool rtl = false;
209 bool changed = false;
210
211 if (sb->type == NWID_HSCROLLBAR) {
212 pos = x;
213 rtl = _current_text_dir == TD_RTL;
214 button_size = NWidgetScrollbar::GetHorizontalDimension().width;
215 } else {
216 pos = y;
217 button_size = NWidgetScrollbar::GetVerticalDimension().height;
218 }
219 if (pos < mi + button_size) {
220 /* Pressing the upper button? */
222 if (_scroller_click_timeout <= 1) {
223 _scroller_click_timeout = 3;
224 changed = sb->UpdatePosition(rtl ? 1 : -1);
225 }
226 w->mouse_capture_widget = sb->GetIndex();
227 } else if (pos >= ma - button_size) {
228 /* Pressing the lower button? */
230
231 if (_scroller_click_timeout <= 1) {
232 _scroller_click_timeout = 3;
233 changed = sb->UpdatePosition(rtl ? -1 : 1);
234 }
235 w->mouse_capture_widget = sb->GetIndex();
236 } else {
237 auto [start, end] = HandleScrollbarHittest(sb, mi, ma, sb->type == NWID_HSCROLLBAR);
238
239 if (pos < start) {
240 changed = sb->UpdatePosition(rtl ? 1 : -1, Scrollbar::Stepping::Big);
241 } else if (pos > end) {
242 changed = sb->UpdatePosition(rtl ? -1 : 1, Scrollbar::Stepping::Big);
243 } else {
244 _scrollbar_start_pos = start - mi - button_size;
245 _scrollbar_size = ma - mi - button_size * 2 - (end - start);
246 w->mouse_capture_widget = sb->GetIndex();
247 _cursorpos_drag_start = _cursor.pos;
248 }
249 }
250
251 if (changed) {
252 /* Position changed so refresh the window */
253 w->OnScrollbarScroll(sb->GetIndex());
254 w->SetDirty();
255 } else {
256 /* No change so only refresh this scrollbar */
257 sb->SetDirty(w);
258 }
259}
260
269void ScrollbarClickHandler(Window *w, NWidgetCore *nw, int x, int y)
270{
271 int mi, ma;
272
273 if (nw->type == NWID_HSCROLLBAR) {
274 mi = nw->pos_x;
275 ma = nw->pos_x + nw->current_x;
276 } else {
277 mi = nw->pos_y;
278 ma = nw->pos_y + nw->current_y;
279 }
280 NWidgetScrollbar *scrollbar = dynamic_cast<NWidgetScrollbar*>(nw);
281 assert(scrollbar != nullptr);
282 ScrollbarClickPositioning(w, scrollbar, x, y, mi, ma);
283}
284
293WidgetID GetWidgetFromPos(const Window *w, int x, int y)
294{
295 NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
296 return (nw != nullptr) ? nw->GetIndex() : INVALID_WIDGET;
297}
298
308void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
309{
310 if (flags.Test(FrameFlag::Transparent)) {
312 } else {
313 assert(colour < Colours::End);
314
315 const PixelColour dark = GetColourGradient(colour, Shade::Dark);
316 const PixelColour medium_dark = GetColourGradient(colour, Shade::Light);
317 const PixelColour medium_light = GetColourGradient(colour, Shade::Lighter);
318 const PixelColour light = GetColourGradient(colour, Shade::Lightest);
319 PixelColour interior;
320
321 Rect outer = {left, top, right, bottom}; // Outside rectangle
322 Rect inner = outer.Shrink(WidgetDimensions::scaled.bevel); // Inside rectangle
323
324 if (flags.Test(FrameFlag::Lowered)) {
325 GfxFillRect(outer.left, outer.top, inner.left - 1, outer.bottom, dark); // Left
326 GfxFillRect(inner.left, outer.top, outer.right, inner.top - 1, dark); // Top
327 GfxFillRect(inner.right + 1, inner.top, outer.right, inner.bottom, light); // Right
328 GfxFillRect(inner.left, inner.bottom + 1, outer.right, outer.bottom, light); // Bottom
329 interior = (flags.Test(FrameFlag::Darkened) ? medium_dark : medium_light);
330 } else {
331 GfxFillRect(outer.left, outer.top, inner.left - 1, inner.bottom, light); // Left
332 GfxFillRect(inner.left, outer.top, inner.right, inner.top - 1, light); // Top
333 GfxFillRect(inner.right + 1, outer.top, outer.right, inner.bottom, dark); // Right
334 GfxFillRect(outer.left, inner.bottom + 1, outer.right, outer.bottom, dark); // Bottom
335 interior = medium_dark;
336 }
337 if (!flags.Test(FrameFlag::BorderOnly)) {
338 GfxFillRect(inner.left, inner.top, inner.right, inner.bottom, interior); // Inner
339 }
340 }
341}
342
351{
352 Point offset;
353 Dimension d = GetSpriteSize(img, &offset);
354 d.width -= offset.x;
355 d.height -= offset.y;
356
357 Point p = GetAlignedPosition(r, d, align);
358 DrawSprite(img, pal, p.x - offset.x, p.y - offset.y);
359}
360
370static inline void DrawImageButtons(const Rect &r, WidgetType type, Colours colour, bool clicked, SpriteID img, Alignment align)
371{
372 assert(img != 0);
373 DrawFrameRect(r, colour, clicked ? FrameFlag::Lowered : FrameFlags{});
374
375 if ((type & WWT_MASK) == WWT_IMGBTN_2 && clicked) img++; // Show different image when clicked for #WWT_IMGBTN_2.
376 DrawSpriteIgnorePadding(img, PAL_NONE, r, align);
377}
378
390static inline void DrawImageTextButtons(const Rect &r, Colours colour, bool clicked, SpriteID img, TextColour text_colour, const std::string &text, Alignment align, FontSize fs)
391{
392 DrawFrameRect(r, colour, clicked ? FrameFlag::Lowered : FrameFlags{});
393
394 bool rtl = _current_text_dir == TD_RTL;
395 int image_width = img != 0 ? std::max<int>(GetSquareScaledSpriteSize(img).width, r.Shrink(WidgetDimensions::scaled.framerect).Height()) : 0;
396 Rect r_img = r.Shrink(WidgetDimensions::scaled.framerect).WithWidth(image_width, rtl);
397 Rect r_text = r.Shrink(WidgetDimensions::scaled.framerect).Indent(image_width + WidgetDimensions::scaled.hsep_wide, rtl);
398
399 if (img != 0) {
400 DrawSpriteIgnorePadding(img, PAL_NONE, r_img, {AlignmentH::Centre, align.v});
401 }
402
403 if (!text.empty()) {
404 Dimension d = GetStringBoundingBox(text, fs);
405 Point p = GetAlignedPosition(r_text, d, align);
406 DrawString(r_text.left, r_text.right, p.y, text, text_colour, align, false, fs);
407 }
408}
409
418static inline void DrawLabel(const Rect &r, TextColour colour, std::string_view str, Alignment align, FontSize fs)
419{
420 if (str.empty()) return;
421
422 Dimension d = GetStringBoundingBox(str, fs);
423 Point p = GetAlignedPosition(r, d, align);
424 DrawString(r.left, r.right, p.y, str, colour, align, false, fs);
425}
426
435static inline void DrawText(const Rect &r, TextColour colour, std::string_view str, Alignment align, FontSize fs)
436{
437 if (str.empty()) return;
438
439 Dimension d = GetStringBoundingBox(str, fs);
440 Point p = GetAlignedPosition(r, d, align);
441 DrawString(r.left, r.right, p.y, str, colour, align, false, fs);
442}
443
453static inline void DrawInset(const Rect &r, Colours colour, TextColour text_colour, std::string_view str, Alignment align, FontSize fs)
454{
456 if (!str.empty()) DrawString(r.Shrink(WidgetDimensions::scaled.inset), str, text_colour, align, false, fs);
457}
458
469static inline void DrawMatrix(const Rect &r, Colours colour, bool clicked, uint32_t num_columns, uint32_t num_rows, uint resize_x, uint resize_y)
470{
471 DrawFrameRect(r, colour, clicked ? FrameFlag::Lowered : FrameFlags{});
472
473 int column_width; // Width of a single column in the matrix.
474 if (num_columns == 0) {
475 column_width = resize_x;
476 num_columns = r.Width() / column_width;
477 } else {
478 column_width = r.Width() / num_columns;
479 }
480
481 int row_height; // Height of a single row in the matrix.
482 if (num_rows == 0) {
483 row_height = resize_y;
484 num_rows = r.Height() / row_height;
485 } else {
486 row_height = r.Height() / num_rows;
487 }
488
490
491 int x = r.left;
492 for (int ctr = num_columns; ctr > 1; ctr--) {
493 x += column_width;
494 GfxFillRect(x, r.top + WidgetDimensions::scaled.bevel.top, x + WidgetDimensions::scaled.bevel.left - 1, r.bottom - WidgetDimensions::scaled.bevel.bottom, col);
495 }
496
497 x = r.top;
498 for (int ctr = num_rows; ctr > 1; ctr--) {
499 x += row_height;
500 GfxFillRect(r.left + WidgetDimensions::scaled.bevel.left, x, r.right - WidgetDimensions::scaled.bevel.right, x + WidgetDimensions::scaled.bevel.top - 1, col);
501 }
502
503 col = GetColourGradient(colour, Shade::Normal);
504
505 x = r.left - 1;
506 for (int ctr = num_columns; ctr > 1; ctr--) {
507 x += column_width;
508 GfxFillRect(x - WidgetDimensions::scaled.bevel.right + 1, r.top + WidgetDimensions::scaled.bevel.top, x, r.bottom - WidgetDimensions::scaled.bevel.bottom, col);
509 }
510
511 x = r.top - 1;
512 for (int ctr = num_rows; ctr > 1; ctr--) {
513 x += row_height;
514 GfxFillRect(r.left + WidgetDimensions::scaled.bevel.left, x - WidgetDimensions::scaled.bevel.bottom + 1, r.right - WidgetDimensions::scaled.bevel.right, x, col);
515 }
516}
517
527static inline void DrawVerticalScrollbar(const Rect &r, Colours colour, bool up_clicked, bool bar_dragged, bool down_clicked, const Scrollbar *scrollbar)
528{
529 int height = NWidgetScrollbar::GetVerticalDimension().height;
530
531 /* draw up/down buttons */
532 DrawImageButtons(r.WithHeight(height, false), NWID_VSCROLLBAR, colour, up_clicked, SPR_ARROW_UP, {AlignmentH::Centre, AlignmentV::Middle});
533 DrawImageButtons(r.WithHeight(height, true), NWID_VSCROLLBAR, colour, down_clicked, SPR_ARROW_DOWN, {AlignmentH::Centre, AlignmentV::Middle});
534
537
538 /* draw "shaded" background */
539 Rect bg = r.Shrink(0, height);
540 GfxFillRect(bg, c2);
542
543 /* track positions. These fractions are based on original 1x dimensions, but scale better. */
544 int left = r.left + r.Width() * 3 / 11; /* left track is positioned 3/11ths from the left */
545 int right = r.left + r.Width() * 8 / 11; /* right track is positioned 8/11ths from the left */
546 const uint8_t bl = WidgetDimensions::scaled.bevel.left;
547 const uint8_t br = WidgetDimensions::scaled.bevel.right;
548
549 /* draw shaded lines */
550 GfxFillRect(bg.WithX(left - bl, left - 1), c1);
551 GfxFillRect(bg.WithX(left, left + br - 1), c2);
552 GfxFillRect(bg.WithX(right - bl, right - 1), c1);
553 GfxFillRect(bg.WithX(right, right + br - 1), c2);
554
555 auto [top, bottom] = HandleScrollbarHittest(scrollbar, r.top, r.bottom, false);
556 DrawFrameRect(r.left, top, r.right, bottom, colour, bar_dragged ? FrameFlag::Lowered : FrameFlags{});
557}
558
568static inline void DrawHorizontalScrollbar(const Rect &r, Colours colour, bool left_clicked, bool bar_dragged, bool right_clicked, const Scrollbar *scrollbar)
569{
570 int width = NWidgetScrollbar::GetHorizontalDimension().width;
571
572 DrawImageButtons(r.WithWidth(width, false), NWID_HSCROLLBAR, colour, left_clicked, SPR_ARROW_LEFT, {AlignmentH::Centre, AlignmentV::Middle});
573 DrawImageButtons(r.WithWidth(width, true), NWID_HSCROLLBAR, colour, right_clicked, SPR_ARROW_RIGHT, {AlignmentH::Centre, AlignmentV::Middle});
574
577
578 /* draw "shaded" background */
579 Rect bg = r.Shrink(width, 0);
580 GfxFillRect(bg, c2);
582
583 /* track positions. These fractions are based on original 1x dimensions, but scale better. */
584 int top = r.top + r.Height() * 3 / 11; /* top track is positioned 3/11ths from the top */
585 int bottom = r.top + r.Height() * 8 / 11; /* bottom track is positioned 8/11ths from the top */
586 const uint8_t bt = WidgetDimensions::scaled.bevel.top;
587 const uint8_t bb = WidgetDimensions::scaled.bevel.bottom;
588
589 /* draw shaded lines */
590 GfxFillRect(bg.WithY(top - bt, top - 1), c1);
591 GfxFillRect(bg.WithY(top, top + bb - 1), c2);
592 GfxFillRect(bg.WithY(bottom - bt, bottom - 1), c1);
593 GfxFillRect(bg.WithY(bottom, bottom + bb - 1), c2);
594
595 /* draw actual scrollbar */
596 auto [left, right] = HandleScrollbarHittest(scrollbar, r.left, r.right, true);
597 DrawFrameRect(left, r.top, right, r.bottom, colour, bar_dragged ? FrameFlag::Lowered : FrameFlags{});
598}
599
609static inline void DrawFrame(const Rect &r, Colours colour, TextColour text_colour, std::string_view str, Alignment align, FontSize fs)
610{
611 int x2 = r.left; // by default the left side is the left side of the widget
612
613 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);
614
617
618 /* If the frame has text, adjust the top bar to fit half-way through */
619 Rect inner = r.Shrink(ScaleGUITrad(1));
620 if (!str.empty()) inner.top = r.top + GetCharacterHeight(FontSize::Normal) / 2;
621
622 Rect outer = inner.Expand(WidgetDimensions::scaled.bevel);
623 Rect inside = inner.Shrink(WidgetDimensions::scaled.bevel);
624
625 if (_current_text_dir == TD_LTR) {
626 /* Line from upper left corner to start of text */
627 GfxFillRect(outer.left, outer.top, r.left + WidgetDimensions::scaled.frametext.left - WidgetDimensions::scaled.bevel.left - 1, inner.top - 1, c1);
628 GfxFillRect(inner.left, inner.top, r.left + WidgetDimensions::scaled.frametext.left - WidgetDimensions::scaled.bevel.left - 1, inside.top - 1, c2);
629
630 /* Line from end of text to upper right corner */
631 GfxFillRect(x2 + WidgetDimensions::scaled.bevel.right, outer.top, inner.right, inner.top - 1, c1);
632 GfxFillRect(x2 + WidgetDimensions::scaled.bevel.right, inner.top, inside.right, inside.top - 1, c2);
633 } else {
634 /* Line from upper left corner to start of text */
635 GfxFillRect(outer.left, outer.top, x2 - WidgetDimensions::scaled.bevel.left - 1, inner.top - 1, c1);
636 GfxFillRect(inner.left, inner.top, x2 - WidgetDimensions::scaled.bevel.left - 1, inside.top - 1, c2);
637
638 /* Line from end of text to upper right corner */
639 GfxFillRect(r.right - WidgetDimensions::scaled.frametext.right + WidgetDimensions::scaled.bevel.right, outer.top, inner.right, inner.top - 1, c1);
640 GfxFillRect(r.right - WidgetDimensions::scaled.frametext.right + WidgetDimensions::scaled.bevel.right, inner.top, inside.right, inside.top - 1, c2);
641 }
642
643 /* Line from upper left corner to bottom left corner */
644 GfxFillRect(outer.left, inner.top, inner.left - 1, inner.bottom, c1);
645 GfxFillRect(inner.left, inside.top, inside.left - 1, inside.bottom, c2);
646
647 /* Line from upper right corner to bottom right corner */
648 GfxFillRect(inside.right + 1, inner.top, inner.right, inside.bottom, c1);
649 GfxFillRect(inner.right + 1, outer.top, outer.right, inner.bottom, c2);
650
651 /* Line from bottom left corner to bottom right corner */
652 GfxFillRect(inner.left, inside.bottom + 1, inner.right, inner.bottom, c1);
653 GfxFillRect(outer.left, inner.bottom + 1, outer.right, outer.bottom, c2);
654}
655
662static inline void DrawShadeBox(const Rect &r, Colours colour, bool clicked)
663{
665}
666
673static inline void DrawStickyBox(const Rect &r, Colours colour, bool clicked)
674{
675 DrawImageButtons(r, WWT_STICKYBOX, colour, clicked, clicked ? SPR_PIN_UP : SPR_PIN_DOWN, {AlignmentH::Centre, AlignmentV::Middle});
676}
677
684static inline void DrawDefSizeBox(const Rect &r, Colours colour, bool clicked)
685{
686 DrawImageButtons(r, WWT_DEFSIZEBOX, colour, clicked, SPR_WINDOW_DEFSIZE, {AlignmentH::Centre, AlignmentV::Middle});
687}
688
695static inline void DrawDebugBox(const Rect &r, Colours colour, bool clicked)
696{
698}
699
708static inline void DrawResizeBox(const Rect &r, Colours colour, bool at_left, bool clicked, bool bevel)
709{
710 if (bevel) {
711 DrawFrameRect(r, colour, clicked ? FrameFlag::Lowered : FrameFlags{});
712 } else if (clicked) {
714 }
716}
717
723static inline void DrawCloseBox(const Rect &r, Colours colour)
724{
725 if (colour != Colours::White) DrawFrameRect(r, colour, {});
726 Point offset;
727 Dimension d = GetSpriteSize(SPR_CLOSEBOX, &offset);
728 d.width -= offset.x;
729 d.height -= offset.y;
730 int s = ScaleSpriteTrad(1); // Offset to account for shadow of SPR_CLOSEBOX.
731 DrawSprite(SPR_CLOSEBOX, to_underlying(colour != Colours::White ? TextColour::Black : TextColour::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);
732}
733
744void DrawCaption(const Rect &r, Colours colour, Owner owner, TextColour text_colour, std::string_view str, Alignment align, FontSize fs)
745{
746 bool company_owned = owner < MAX_COMPANIES;
747
751
752 if (company_owned) {
754 }
755
756 if (str.empty()) return;
757
759 Point p = GetAlignedPosition(r, d, align);
760 DrawString(r.left + WidgetDimensions::scaled.captiontext.left, r.right - WidgetDimensions::scaled.captiontext.left, p.y, str, text_colour, align, false, fs);
761}
762
774static inline void DrawButtonDropdown(const Rect &r, Colours colour, bool clicked_button, bool clicked_dropdown, std::string_view str, Alignment align)
775{
776 bool rtl = _current_text_dir == TD_RTL;
777
778 Rect text = r.Indent(NWidgetLeaf::dropdown_dimension.width, !rtl);
779 DrawFrameRect(text, colour, clicked_button ? FrameFlag::Lowered : FrameFlags{});
780 if (!str.empty()) {
781 text = text.CentreToHeight(GetCharacterHeight(FontSize::Normal)).Shrink(WidgetDimensions::scaled.dropdowntext, RectPadding::zero);
782 DrawString(text, str, TextColour::Black, align);
783 }
784
785 Rect button = r.WithWidth(NWidgetLeaf::dropdown_dimension.width, !rtl);
787}
788
793{
794 this->nested_root->Draw(this);
795
796 if (this->flags.Test(WindowFlag::WhiteBorder)) {
797 DrawFrameRect(0, 0, this->width - 1, this->height - 1, Colours::White, FrameFlag::BorderOnly);
798 }
799
800 if (this->flags.Test(WindowFlag::Highlighted)) {
801 extern bool _window_highlight_colour;
802 for (const auto &pair : this->widget_lookup) {
803 const NWidgetBase *widget = pair.second;
804 if (!widget->IsHighlighted()) continue;
805
806 Rect outer = widget->GetCurrentRect();
807 Rect inner = outer.Shrink(WidgetDimensions::scaled.bevel).Expand(1);
808
810
811 GfxFillRect(outer.left, outer.top, inner.left, inner.bottom, colour);
812 GfxFillRect(inner.left + 1, outer.top, inner.right - 1, inner.top, colour);
813 GfxFillRect(inner.right, outer.top, outer.right, inner.bottom, colour);
814 GfxFillRect(outer.left + 1, inner.bottom, outer.right - 1, outer.bottom, colour);
815 }
816 }
817}
818
824void Window::DrawSortButton(WidgetID widget, bool descending) const
825{
826 assert(!this->widget_lookup.empty());
827 Rect r = this->GetWidget<NWidgetBase>(widget)->GetCurrentRect();
828
829 /* Sort button uses the same sprites as vertical scrollbar */
830 Dimension dim = NWidgetScrollbar::GetVerticalDimension();
831
832 DrawSpriteIgnorePadding(descending ? SPR_ARROW_DOWN : SPR_ARROW_UP, PAL_NONE, r.WithWidth(dim.width, _current_text_dir == TD_LTR), {AlignmentH::Centre, AlignmentV::Middle});
833}
834
840{
841 return NWidgetScrollbar::GetVerticalDimension().width + 1;
842}
843
844bool _draw_widget_outlines;
845
846static void DrawOutline(const Window *, const NWidgetBase *wid)
847{
848 if (!_draw_widget_outlines || wid->current_x == 0 || wid->current_y == 0) return;
849
850 DrawRectOutline(wid->GetCurrentRect(), PC_WHITE, 1, 4);
851}
852
905
919
933
940{
941 if (this->index >= 0) widget_lookup[this->index] = this;
942}
943
950
955void NWidgetBase::SetDirty(const Window *w) const
956{
957 int abs_left = w->left + this->pos_x;
958 int abs_top = w->top + this->pos_y;
959 AddDirtyBlock(abs_left, abs_top, abs_left + this->current_x, abs_top + this->current_y);
960}
961
969
976{
977 return (this->type == tp) ? this : nullptr;
978}
979
980void NWidgetBase::ApplyAspectRatio()
981{
982 if (this->aspect_ratio == 0) return;
983 if (this->smallest_x == 0 || this->smallest_y == 0) return;
984
985 uint x = this->smallest_x;
986 uint y = this->smallest_y;
987 if (this->aspect_flags.Test(AspectFlag::ResizeX)) x = std::max(this->smallest_x, static_cast<uint>(this->smallest_y * std::abs(this->aspect_ratio)));
988 if (this->aspect_flags.Test(AspectFlag::ResizeY)) y = std::max(this->smallest_y, static_cast<uint>(this->smallest_x / std::abs(this->aspect_ratio)));
989
990 this->smallest_x = x;
991 this->smallest_y = y;
992}
993
998
1007{
1008 this->fill_x = fill_x;
1009 this->fill_y = fill_y;
1010}
1011
1018{
1019 this->aspect_ratio = ratio;
1020 this->aspect_flags = flags;
1021}
1022
1029void NWidgetResizeBase::SetAspect(int x_ratio, int y_ratio, AspectFlags flags)
1030{
1031 assert(x_ratio > 0 && y_ratio > 0);
1032 this->SetAspect(static_cast<float>(x_ratio) / static_cast<float>(y_ratio), flags);
1033}
1034
1036{
1037 if (!this->absolute) {
1038 this->min_x = ScaleGUITrad(this->uz_min_x);
1039 this->min_y = std::max(ScaleGUITrad(this->uz_min_y), this->uz_text_lines * GetCharacterHeight(this->uz_text_size) + ScaleGUITrad(this->uz_text_spacing));
1040 if (this->toolbar_size > 0) {
1041 this->min_x = std::max(this->min_x, this->toolbar_size * _toolbar_image_size.width + WidgetDimensions::scaled.imgbtn.Horizontal());
1042 this->min_y = std::max(this->min_y, _toolbar_image_size.height + WidgetDimensions::scaled.imgbtn.Vertical());
1043 }
1044 }
1046}
1047
1054{
1055 this->uz_min_x = std::max(this->uz_min_x, min_x);
1056 this->uz_min_y = std::max(this->uz_min_y, min_y);
1057}
1058
1064{
1065 this->toolbar_size = toolbar_size;
1066}
1067
1074{
1075 this->absolute = true;
1076 this->min_x = std::max(this->min_x, min_x);
1077 this->min_y = std::max(this->min_y, min_y);
1078}
1079
1086void NWidgetResizeBase::SetMinimalTextLines(uint8_t min_lines, uint8_t spacing, FontSize size)
1087{
1088 this->uz_text_lines = min_lines;
1089 this->uz_text_spacing = spacing;
1090 this->uz_text_size = size;
1091}
1092
1099{
1100 this->fill_x = fill_x;
1101 this->fill_y = fill_y;
1102}
1103
1110{
1111 this->resize_x = resize_x;
1112 this->resize_y = resize_y;
1113}
1114
1122bool NWidgetResizeBase::UpdateMultilineWidgetSize(const std::string &str, int max_lines)
1123{
1124 int y = GetStringHeight(str, this->current_x);
1125 if (y > max_lines * GetCharacterHeight(FontSize::Normal)) {
1126 /* Text at the current width is too tall, so try to guess a better width. */
1128 d.height *= max_lines;
1129 d.width /= 2;
1130 return this->UpdateSize(d.width, d.height);
1131 }
1132 return this->UpdateVerticalSize(y);
1133}
1134
1143{
1144 if (min_x == this->min_x && min_y == this->min_y) return false;
1145 this->min_x = min_x;
1146 this->min_y = min_y;
1147 return true;
1148}
1149
1157{
1158 if (min_y == this->min_y) return false;
1159 this->min_y = min_y;
1160 return true;
1161}
1162
1163void NWidgetResizeBase::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool)
1164{
1165 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1166}
1167
1179{
1180 this->colour = colour;
1181 this->widget_data = widget_data;
1182 this->SetToolTip(tool_tip);
1184}
1185
1191{
1192 this->widget_data.string = string;
1193}
1194
1201{
1202 this->SetString(string);
1203 this->SetToolTip(tool_tip);
1204}
1205
1211{
1212 this->widget_data.sprite = sprite;
1213}
1214
1221{
1222 this->SetSprite(sprite);
1223 this->SetToolTip(tool_tip);
1224}
1225
1231void NWidgetCore::SetMatrixDimension(uint32_t columns, uint32_t rows)
1232{
1233 this->widget_data.matrix = { columns, rows };
1234}
1235
1241{
1242 this->widget_data.resize_widget_type = type;
1243}
1244
1251{
1252 this->text_colour = colour;
1253 this->text_size = size;
1254}
1255
1261{
1262 this->tool_tip = tool_tip;
1263}
1264
1270{
1271 return this->tool_tip;
1272}
1273
1279{
1280 this->align = align;
1281}
1282
1288{
1289 return this->widget_data.string;
1290}
1291
1297{
1298 return this->scrollbar_index;
1299}
1300
1302{
1303 return (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) ? this : nullptr;
1304}
1305
1307{
1308 if (this->type == tp) return this;
1309 for (const auto &child_wid : this->children) {
1310 NWidgetBase *nwid = child_wid->GetWidgetOfType(tp);
1311 if (nwid != nullptr) return nwid;
1312 }
1313 return nullptr;
1314}
1315
1317{
1318 for (const auto &child_wid : this->children) {
1319 child_wid->AdjustPaddingForZoom();
1320 }
1322}
1323
1328void NWidgetContainer::Add(std::unique_ptr<NWidgetBase> &&wid)
1329{
1330 assert(wid != nullptr);
1331 wid->parent = this;
1332 this->children.push_back(std::move(wid));
1333}
1334
1336{
1337 this->NWidgetBase::FillWidgetLookup(widget_lookup);
1338 for (const auto &child_wid : this->children) {
1339 child_wid->FillWidgetLookup(widget_lookup);
1340 }
1341}
1342
1344{
1345 for (const auto &child_wid : this->children) {
1346 child_wid->Draw(w);
1347 }
1348
1349 DrawOutline(w, this);
1350}
1351
1353{
1354 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
1355
1356 for (const auto &child_wid : this->children) {
1357 NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
1358 if (nwid != nullptr) return nwid;
1359 }
1360 return nullptr;
1361}
1362
1364{
1365 /* Zero size plane selected */
1366 if (this->shown_plane >= SZSP_BEGIN) {
1367 Dimension size = {0, 0};
1368 Dimension padding = {0, 0};
1369 Dimension fill = {(this->shown_plane == SZSP_HORIZONTAL), (this->shown_plane == SZSP_VERTICAL)};
1370 Dimension resize = {(this->shown_plane == SZSP_HORIZONTAL), (this->shown_plane == SZSP_VERTICAL)};
1371 /* Here we're primarily interested in the value of resize */
1372 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
1373
1374 this->smallest_x = size.width;
1375 this->smallest_y = size.height;
1376 this->fill_x = fill.width;
1377 this->fill_y = fill.height;
1378 this->resize_x = resize.width;
1379 this->resize_y = resize.height;
1380 this->ApplyAspectRatio();
1381 return;
1382 }
1383
1384 /* First sweep, recurse down and compute minimal size and filling. */
1385 this->smallest_x = 0;
1386 this->smallest_y = 0;
1387 this->fill_x = this->IsEmpty() ? 0 : 1;
1388 this->fill_y = this->IsEmpty() ? 0 : 1;
1389 this->resize_x = this->IsEmpty() ? 0 : 1;
1390 this->resize_y = this->IsEmpty() ? 0 : 1;
1391 for (const auto &child_wid : this->children) {
1392 child_wid->SetupSmallestSize(w);
1393
1394 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1395 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1396 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1397 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1398 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1399 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1400 this->ApplyAspectRatio();
1401 }
1402}
1403
1404void NWidgetStacked::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1405{
1406 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1407 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1408
1409 if (this->shown_plane >= SZSP_BEGIN) return;
1410
1411 for (const auto &child_wid : this->children) {
1412 uint hor_step = (sizing == SizingType::Smallest) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1413 uint child_width = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1414 uint child_pos_x = (rtl ? child_wid->padding.right : child_wid->padding.left);
1415
1416 uint vert_step = (sizing == SizingType::Smallest) ? 1 : child_wid->GetVerticalStepSize(sizing);
1417 uint child_height = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1418 uint child_pos_y = child_wid->padding.top;
1419
1420 child_wid->AssignSizePosition(sizing, x + child_pos_x, y + child_pos_y, child_width, child_height, rtl);
1421 }
1422}
1423
1425{
1426 /* We need to update widget_lookup later. */
1427 this->widget_lookup = &widget_lookup;
1428
1429 this->NWidgetContainer::FillWidgetLookup(widget_lookup);
1430 /* In case widget IDs are repeated, make sure Window::GetWidget works on displayed widgets. */
1431 if (static_cast<size_t>(this->shown_plane) < this->children.size()) this->children[shown_plane]->FillWidgetLookup(widget_lookup);
1432}
1433
1435{
1436 if (this->shown_plane >= SZSP_BEGIN) return;
1437
1438 assert(static_cast<size_t>(this->shown_plane) < this->children.size());
1439 this->children[shown_plane]->Draw(w);
1440 DrawOutline(w, this);
1441}
1442
1444{
1445 if (this->shown_plane >= SZSP_BEGIN) return nullptr;
1446
1447 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
1448
1449 if (static_cast<size_t>(this->shown_plane) >= this->children.size()) return nullptr;
1450 return this->children[shown_plane]->GetWidgetFromPos(x, y);
1451}
1452
1459{
1460 if (this->shown_plane == plane) return false;
1461 this->shown_plane = plane;
1462 /* In case widget IDs are repeated, make sure Window::GetWidget works on displayed widgets. */
1463 if (static_cast<size_t>(this->shown_plane) < this->children.size()) this->children[shown_plane]->FillWidgetLookup(*this->widget_lookup);
1464 return true;
1465}
1466
1467class NWidgetLayer : public NWidgetContainer {
1468public:
1469 NWidgetLayer(WidgetID index) : NWidgetContainer(NWID_LAYER, index) {}
1470
1471 void SetupSmallestSize(Window *w) override;
1472 void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override;
1473
1474 void Draw(const Window *w) override;
1475};
1476
1478{
1479 /* First sweep, recurse down and compute minimal size and filling. */
1480 this->smallest_x = 0;
1481 this->smallest_y = 0;
1482 this->fill_x = this->IsEmpty() ? 0 : 1;
1483 this->fill_y = this->IsEmpty() ? 0 : 1;
1484 this->resize_x = this->IsEmpty() ? 0 : 1;
1485 this->resize_y = this->IsEmpty() ? 0 : 1;
1486 for (const auto &child_wid : this->children) {
1487 child_wid->SetupSmallestSize(w);
1488
1489 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1490 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1491 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1492 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1493 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1494 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1495 this->ApplyAspectRatio();
1496 }
1497}
1498
1499void NWidgetLayer::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1500{
1501 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1502 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1503
1504 for (const auto &child_wid : this->children) {
1505 uint hor_step = (sizing == SizingType::Smallest) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1506 uint child_width = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1507 uint child_pos_x = (rtl ? child_wid->padding.right : child_wid->padding.left);
1508
1509 uint vert_step = (sizing == SizingType::Smallest) ? 1 : child_wid->GetVerticalStepSize(sizing);
1510 uint child_height = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1511 uint child_pos_y = child_wid->padding.top;
1512
1513 child_wid->AssignSizePosition(sizing, x + child_pos_x, y + child_pos_y, child_width, child_height, rtl);
1514 }
1515}
1516
1518{
1519 /* Draw in reverse order, as layers are arranged top-down. */
1520 for (auto it = std::rbegin(this->children); it != std::rend(this->children); ++it) {
1521 (*it)->Draw(w);
1522 }
1523
1524 DrawOutline(w, this);
1525}
1526
1534
1545{
1546 this->uz_pip_pre = pip_pre;
1547 this->uz_pip_inter = pip_inter;
1548 this->uz_pip_post = pip_post;
1549
1550 this->pip_pre = ScaleGUITrad(this->uz_pip_pre);
1551 this->pip_inter = ScaleGUITrad(this->uz_pip_inter);
1552 this->pip_post = ScaleGUITrad(this->uz_pip_post);
1553}
1554
1565{
1566 this->pip_ratio_pre = pip_ratio_pre;
1567 this->pip_ratio_inter = pip_ratio_inter;
1568 this->pip_ratio_post = pip_ratio_post;
1569}
1570
1572{
1573 this->smallest_x = 0; // Sum of minimal size of all children.
1574 this->smallest_y = 0; // Biggest child.
1575 this->fill_x = 0; // smallest non-zero child widget fill step.
1576 this->fill_y = 1; // smallest common child fill step.
1577 this->resize_x = 0; // smallest non-zero child widget resize step.
1578 this->resize_y = 1; // smallest common child resize step.
1579 this->gaps = 0;
1580
1581 /* 1a. Forward call, collect longest/widest child length. */
1582 uint longest = 0; // Longest child found.
1583 uint max_vert_fill = 0; // Biggest vertical fill step.
1584 for (const auto &child_wid : this->children) {
1585 child_wid->SetupSmallestSize(w);
1586 longest = std::max(longest, child_wid->smallest_x);
1587 max_vert_fill = std::max(max_vert_fill, child_wid->GetVerticalStepSize(SizingType::Smallest));
1588 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding.Vertical());
1589 if (child_wid->smallest_x != 0 || child_wid->fill_x != 0) this->gaps++;
1590 }
1591 if (this->gaps > 0) this->gaps--; // Number of gaps is number of widgets less one.
1592 /* 1b. Make the container higher if needed to accommodate all children nicely. */
1593 [[maybe_unused]] uint max_smallest = this->smallest_y + 3 * max_vert_fill; // Upper limit to computing smallest height.
1594 uint cur_height = this->smallest_y;
1595 for (;;) {
1596 for (const auto &child_wid : this->children) {
1597 uint step_size = child_wid->GetVerticalStepSize(SizingType::Smallest);
1598 uint child_height = child_wid->smallest_y + child_wid->padding.Vertical();
1599 if (step_size > 1 && child_height < cur_height) { // Small step sizes or already fitting children are not interesting.
1600 uint remainder = (cur_height - child_height) % step_size;
1601 if (remainder > 0) { // Child did not fit entirely, widen the container.
1602 cur_height += step_size - remainder;
1603 assert(cur_height < max_smallest); // Safeguard against infinite height expansion.
1604 /* Remaining children will adapt to the new cur_height, thus speeding up the computation. */
1605 }
1606 }
1607 }
1608 if (this->smallest_y == cur_height) break;
1609 this->smallest_y = cur_height; // Smallest height got changed, try again.
1610 }
1611 /* 2. For containers that must maintain equal width, extend child minimal size. */
1612 for (const auto &child_wid : this->children) {
1613 child_wid->smallest_y = this->smallest_y - child_wid->padding.Vertical();
1614 child_wid->ApplyAspectRatio();
1615 longest = std::max(longest, child_wid->smallest_x);
1616 }
1617 if (this->flags.Test(NWidContainerFlag::EqualSize)) {
1618 for (const auto &child_wid : this->children) {
1619 if (child_wid->fill_x == 1) child_wid->smallest_x = longest;
1620 }
1621 }
1622 /* 3. Compute smallest, fill, and resize values of the container. */
1623 for (const auto &child_wid : this->children) {
1624 this->smallest_x += child_wid->smallest_x + child_wid->padding.Horizontal();
1625 if (child_wid->fill_x > 0) {
1626 if (this->fill_x == 0 || this->fill_x > child_wid->fill_x) this->fill_x = child_wid->fill_x;
1627 }
1628 this->fill_y = std::lcm(this->fill_y, child_wid->fill_y);
1629
1630 if (child_wid->resize_x > 0) {
1631 if (this->resize_x == 0 || this->resize_x > child_wid->resize_x) this->resize_x = child_wid->resize_x;
1632 }
1633 this->resize_y = std::lcm(this->resize_y, child_wid->resize_y);
1634 }
1635 if (this->fill_x == 0 && this->pip_ratio_pre + this->pip_ratio_inter + this->pip_ratio_post > 0) this->fill_x = 1;
1636 /* 4. Increase by required PIP space. */
1637 this->smallest_x += this->pip_pre + this->gaps * this->pip_inter + this->pip_post;
1638}
1639
1640void NWidgetHorizontal::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1641{
1642 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1643
1644 /* Compute additional width given to us. */
1645 uint additional_length = given_width - (this->pip_pre + this->gaps * this->pip_inter + this->pip_post);
1646 for (const auto &child_wid : this->children) {
1647 if (child_wid->smallest_x != 0 || child_wid->fill_x != 0) additional_length -= child_wid->smallest_x + child_wid->padding.Horizontal();
1648 }
1649
1650 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1651
1652 /* In principle, the additional horizontal space is distributed evenly over the available resizable children. Due to step sizes, this may not always be feasible.
1653 * To make resizing work as good as possible, first children with biggest step sizes are done. These may get less due to rounding down.
1654 * 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
1655 * of the child with the smallest non-zero stepsize.
1656 *
1657 * 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
1658 * size and position, and directly call child->AssignSizePosition() with the computed values.
1659 * 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
1660 * then we call the child.
1661 */
1662
1663 /* First loop: Find biggest stepsize, find number of children that want a piece of the pie, handle vertical size for all children,
1664 * handle horizontal size for non-resizing children.
1665 */
1666 int num_changing_childs = 0; // Number of children that can change size.
1667 uint biggest_stepsize = 0;
1668 for (const auto &child_wid : this->children) {
1669 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1670 if (hor_step > 0) {
1671 if (!flags.Test(NWidContainerFlag::BigFirst)) num_changing_childs++;
1672 biggest_stepsize = std::max(biggest_stepsize, hor_step);
1673 } else {
1674 child_wid->current_x = child_wid->smallest_x;
1675 }
1676
1677 uint vert_step = (sizing == SizingType::Smallest) ? 1 : child_wid->GetVerticalStepSize(sizing);
1678 child_wid->current_y = ComputeMaxSize(child_wid->smallest_y, given_height - child_wid->padding.Vertical(), vert_step);
1679 }
1680
1681 /* First.5 loop: count how many children are of the biggest step size. */
1682 if (flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1683 for (const auto &child_wid : this->children) {
1684 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1685 if (hor_step == biggest_stepsize) {
1686 num_changing_childs++;
1687 }
1688 }
1689 }
1690
1691 /* Second loop: Allocate the additional horizontal space over the resizing children, starting with the biggest resize steps. */
1692 while (biggest_stepsize > 0) {
1693 uint next_biggest_stepsize = 0;
1694 for (const auto &child_wid : this->children) {
1695 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1696 if (hor_step > biggest_stepsize) continue; // Already done
1697 if (hor_step == biggest_stepsize) {
1698 uint increment = additional_length / num_changing_childs;
1699 num_changing_childs--;
1700 if (hor_step > 1) increment -= increment % hor_step;
1701 child_wid->current_x = child_wid->smallest_x + increment;
1702 additional_length -= increment;
1703 continue;
1704 }
1705 next_biggest_stepsize = std::max(next_biggest_stepsize, hor_step);
1706 }
1707 biggest_stepsize = next_biggest_stepsize;
1708
1709 if (num_changing_childs == 0 && flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1710 /* Second.5 loop: count how many children are of the updated biggest step size. */
1711 for (const auto &child_wid : this->children) {
1712 uint hor_step = child_wid->GetHorizontalStepSize(sizing);
1713 if (hor_step == biggest_stepsize) {
1714 num_changing_childs++;
1715 }
1716 }
1717 }
1718 }
1719 assert(num_changing_childs == 0);
1720
1721 uint pre = this->pip_pre;
1722 uint inter = this->pip_inter;
1723
1724 if (additional_length > 0) {
1725 /* Allocate remaining space by pip ratios. If this doesn't round exactly, the unused space will fall into pip_post
1726 * which is never explicitly needed. */
1727 int r = this->pip_ratio_pre + this->gaps * this->pip_ratio_inter + this->pip_ratio_post;
1728 if (r > 0) {
1729 pre += this->pip_ratio_pre * additional_length / r;
1730 if (this->gaps > 0) inter += this->pip_ratio_inter * additional_length / r;
1731 }
1732 }
1733
1734 /* Third loop: Compute position and call the child. */
1735 uint position = rtl ? this->current_x - pre : pre; // Place to put next child relative to origin of the container.
1736 for (const auto &child_wid : this->children) {
1737 uint child_width = child_wid->current_x;
1738 uint child_x = x + (rtl ? position - child_width - child_wid->padding.left : position + child_wid->padding.left);
1739 uint child_y = y + child_wid->padding.top;
1740
1741 child_wid->AssignSizePosition(sizing, child_x, child_y, child_width, child_wid->current_y, rtl);
1742 if (child_wid->current_x != 0) {
1743 uint padded_child_width = child_width + child_wid->padding.Horizontal() + inter;
1744 position = rtl ? position - padded_child_width : position + padded_child_width;
1745 }
1746 }
1747}
1748
1749void NWidgetHorizontalLTR::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool)
1750{
1751 NWidgetHorizontal::AssignSizePosition(sizing, x, y, given_width, given_height, false);
1752}
1753
1755{
1756 this->smallest_x = 0; // Biggest child.
1757 this->smallest_y = 0; // Sum of minimal size of all children.
1758 this->fill_x = 1; // smallest common child fill step.
1759 this->fill_y = 0; // smallest non-zero child widget fill step.
1760 this->resize_x = 1; // smallest common child resize step.
1761 this->resize_y = 0; // smallest non-zero child widget resize step.
1762 this->gaps = 0;
1763
1764 /* 1a. Forward call, collect longest/widest child length. */
1765 uint highest = 0; // Highest child found.
1766 uint max_hor_fill = 0; // Biggest horizontal fill step.
1767 for (const auto &child_wid : this->children) {
1768 child_wid->SetupSmallestSize(w);
1769 highest = std::max(highest, child_wid->smallest_y);
1770 max_hor_fill = std::max(max_hor_fill, child_wid->GetHorizontalStepSize(SizingType::Smallest));
1771 this->smallest_x = std::max(this->smallest_x, child_wid->smallest_x + child_wid->padding.Horizontal());
1772 if (child_wid->smallest_y != 0 || child_wid->fill_y != 0) this->gaps++;
1773 }
1774 if (this->gaps > 0) this->gaps--; // Number of gaps is number of widgets less one.
1775 /* 1b. Make the container wider if needed to accommodate all children nicely. */
1776 [[maybe_unused]] uint max_smallest = this->smallest_x + 3 * max_hor_fill; // Upper limit to computing smallest height.
1777 uint cur_width = this->smallest_x;
1778 for (;;) {
1779 for (const auto &child_wid : this->children) {
1780 uint step_size = child_wid->GetHorizontalStepSize(SizingType::Smallest);
1781 uint child_width = child_wid->smallest_x + child_wid->padding.Horizontal();
1782 if (step_size > 1 && child_width < cur_width) { // Small step sizes or already fitting children are not interesting.
1783 uint remainder = (cur_width - child_width) % step_size;
1784 if (remainder > 0) { // Child did not fit entirely, widen the container.
1785 cur_width += step_size - remainder;
1786 assert(cur_width < max_smallest); // Safeguard against infinite width expansion.
1787 /* Remaining children will adapt to the new cur_width, thus speeding up the computation. */
1788 }
1789 }
1790 }
1791 if (this->smallest_x == cur_width) break;
1792 this->smallest_x = cur_width; // Smallest width got changed, try again.
1793 }
1794 /* 2. For containers that must maintain equal width, extend children minimal size. */
1795 for (const auto &child_wid : this->children) {
1796 child_wid->smallest_x = this->smallest_x - child_wid->padding.Horizontal();
1797 child_wid->ApplyAspectRatio();
1798 highest = std::max(highest, child_wid->smallest_y);
1799 }
1800 if (this->flags.Test(NWidContainerFlag::EqualSize)) {
1801 for (const auto &child_wid : this->children) {
1802 if (child_wid->fill_y == 1) child_wid->smallest_y = highest;
1803 }
1804 }
1805 /* 3. Compute smallest, fill, and resize values of the container. */
1806 for (const auto &child_wid : this->children) {
1807 this->smallest_y += child_wid->smallest_y + child_wid->padding.Vertical();
1808 if (child_wid->fill_y > 0) {
1809 if (this->fill_y == 0 || this->fill_y > child_wid->fill_y) this->fill_y = child_wid->fill_y;
1810 }
1811 this->fill_x = std::lcm(this->fill_x, child_wid->fill_x);
1812
1813 if (child_wid->resize_y > 0) {
1814 if (this->resize_y == 0 || this->resize_y > child_wid->resize_y) this->resize_y = child_wid->resize_y;
1815 }
1816 this->resize_x = std::lcm(this->resize_x, child_wid->resize_x);
1817 }
1818 if (this->fill_y == 0 && this->pip_ratio_pre + this->pip_ratio_inter + this->pip_ratio_post > 0) this->fill_y = 1;
1819 /* 4. Increase by required PIP space. */
1820 this->smallest_y += this->pip_pre + this->gaps * this->pip_inter + this->pip_post;
1821}
1822
1823void NWidgetVertical::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
1824{
1825 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
1826
1827 /* Compute additional height given to us. */
1828 uint additional_length = given_height - (this->pip_pre + this->gaps * this->pip_inter + this->pip_post);
1829 for (const auto &child_wid : this->children) {
1830 if (child_wid->smallest_y != 0 || child_wid->fill_y != 0) additional_length -= child_wid->smallest_y + child_wid->padding.Vertical();
1831 }
1832
1833 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1834
1835 /* Like the horizontal container, the vertical container also distributes additional height evenly, starting with the children with the biggest resize steps.
1836 * It also stores computed widths and heights into current_x and current_y values of the child.
1837 */
1838
1839 /* 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. */
1840 int num_changing_childs = 0; // Number of children that can change size.
1841 uint biggest_stepsize = 0;
1842 for (const auto &child_wid : this->children) {
1843 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1844 if (vert_step > 0) {
1845 if (!flags.Test(NWidContainerFlag::BigFirst)) num_changing_childs++;
1846 biggest_stepsize = std::max(biggest_stepsize, vert_step);
1847 } else {
1848 child_wid->current_y = child_wid->smallest_y;
1849 }
1850
1851 uint hor_step = (sizing == SizingType::Smallest) ? 1 : child_wid->GetHorizontalStepSize(sizing);
1852 child_wid->current_x = ComputeMaxSize(child_wid->smallest_x, given_width - child_wid->padding.Horizontal(), hor_step);
1853 }
1854
1855 /* First.5 loop: count how many children are of the biggest step size. */
1856 if (this->flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1857 for (const auto &child_wid : this->children) {
1858 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1859 if (vert_step == biggest_stepsize) {
1860 num_changing_childs++;
1861 }
1862 }
1863 }
1864
1865 /* Second loop: Allocate the additional vertical space over the resizing children, starting with the biggest resize steps. */
1866 while (biggest_stepsize > 0) {
1867 uint next_biggest_stepsize = 0;
1868 for (const auto &child_wid : this->children) {
1869 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1870 if (vert_step > biggest_stepsize) continue; // Already done
1871 if (vert_step == biggest_stepsize) {
1872 uint increment = additional_length / num_changing_childs;
1873 num_changing_childs--;
1874 if (vert_step > 1) increment -= increment % vert_step;
1875 child_wid->current_y = child_wid->smallest_y + increment;
1876 additional_length -= increment;
1877 continue;
1878 }
1879 next_biggest_stepsize = std::max(next_biggest_stepsize, vert_step);
1880 }
1881 biggest_stepsize = next_biggest_stepsize;
1882
1883 if (num_changing_childs == 0 && flags.Test(NWidContainerFlag::BigFirst) && biggest_stepsize > 0) {
1884 /* Second.5 loop: count how many children are of the updated biggest step size. */
1885 for (const auto &child_wid : this->children) {
1886 uint vert_step = child_wid->GetVerticalStepSize(sizing);
1887 if (vert_step == biggest_stepsize) {
1888 num_changing_childs++;
1889 }
1890 }
1891 }
1892 }
1893 assert(num_changing_childs == 0);
1894
1895 uint pre = this->pip_pre;
1896 uint inter = this->pip_inter;
1897
1898 if (additional_length > 0) {
1899 /* Allocate remaining space by pip ratios. If this doesn't round exactly, the unused space will fall into pip_post
1900 * which is never explicitly needed. */
1901 int r = this->pip_ratio_pre + this->gaps * this->pip_ratio_inter + this->pip_ratio_post;
1902 if (r > 0) {
1903 pre += this->pip_ratio_pre * additional_length / r;
1904 if (this->gaps > 0) inter += this->pip_ratio_inter * additional_length / r;
1905 }
1906 }
1907
1908 /* Third loop: Compute position and call the child. */
1909 uint position = this->bottom_up ? this->current_y - pre : pre; // Place to put next child relative to origin of the container.
1910 for (const auto &child_wid : this->children) {
1911 uint child_height = child_wid->current_y;
1912 uint child_x = x + (rtl ? child_wid->padding.right : child_wid->padding.left);
1913 uint child_y = y + (this->bottom_up ? position - child_height - child_wid->padding.top : position + child_wid->padding.top);
1914
1915 child_wid->AssignSizePosition(sizing, child_x, child_y, child_wid->current_x, child_height, rtl);
1916 if (child_wid->current_y != 0) {
1917 uint padded_child_height = child_height + child_wid->padding.Vertical() + inter;
1918 position = this->bottom_up ? position - padded_child_height : position + padded_child_height;
1919 }
1920 }
1921}
1922
1929{
1930 this->SetMinimalSize(width, height);
1931 this->SetResize(0, 0);
1932}
1933
1935{
1936 this->smallest_x = this->min_x;
1937 this->smallest_y = this->min_y;
1938 this->ApplyAspectRatio();
1939}
1940
1942{
1943 /* Spacer widget is never normally visible. */
1944
1945 if (_draw_widget_outlines && this->current_x != 0 && this->current_y != 0) {
1946 /* Spacers indicate a potential design issue, so get extra highlighting. */
1947 GfxFillRect(this->GetCurrentRect(), PC_WHITE, FillRectMode::Checker);
1948
1949 DrawOutline(w, this);
1950 }
1951}
1952
1954{
1955 /* Spacer widget never need repainting. */
1956}
1957
1959{
1960 return nullptr;
1961}
1962
1968{
1969 this->clicked = clicked;
1970 if (this->clicked >= 0 && this->sb != nullptr && this->widgets_x != 0) {
1971 int vpos = (this->clicked / this->widgets_x) * this->widget_h; // Vertical position of the top.
1972 /* Need to scroll down -> Scroll to the bottom.
1973 * However, last entry has no 'this->pip_inter' underneath, and we must stay below this->sb->GetCount() */
1974 if (this->sb->GetPosition() < vpos) vpos += this->widget_h - this->pip_inter - 1;
1975 this->sb->ScrollTowards(vpos);
1976 }
1977}
1978
1985{
1986 this->count = count;
1987
1988 if (this->sb == nullptr || this->widgets_x == 0) return;
1989
1990 /* We need to get the number of pixels the matrix is high/wide.
1991 * So, determine the number of rows/columns based on the number of
1992 * columns/rows (one is constant/unscrollable).
1993 * Then multiply that by the height of a widget, and add the pre
1994 * and post spacing "offsets". */
1995 count = CeilDiv(count, this->sb->IsVertical() ? this->widgets_x : this->widgets_y);
1996 count *= (this->sb->IsVertical() ? this->children.front()->smallest_y : this->children.front()->smallest_x) + this->pip_inter;
1997 if (count > 0) count -= this->pip_inter; // We counted an inter too much in the multiplication above
1998 count += this->pip_pre + this->pip_post;
1999 this->sb->SetCount(count);
2000 this->sb->SetCapacity(this->sb->IsVertical() ? this->current_y : this->current_x);
2001 this->sb->SetStepSize(this->sb->IsVertical() ? this->widget_h : this->widget_w);
2002}
2003
2009{
2010 this->sb = sb;
2011}
2012
2018{
2019 return this->current_element;
2020}
2021
2023{
2024 assert(this->children.size() == 1);
2025
2026 this->children.front()->SetupSmallestSize(w);
2027
2028 Dimension padding = { (uint)this->pip_pre + this->pip_post, (uint)this->pip_pre + this->pip_post};
2029 Dimension size = {this->children.front()->smallest_x + padding.width, this->children.front()->smallest_y + padding.height};
2030 Dimension fill = {0, 0};
2031 Dimension resize = {this->pip_inter + this->children.front()->smallest_x, this->pip_inter + this->children.front()->smallest_y};
2032
2033 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
2034
2035 this->smallest_x = size.width;
2036 this->smallest_y = size.height;
2037 this->fill_x = fill.width;
2038 this->fill_y = fill.height;
2039 this->resize_x = resize.width;
2040 this->resize_y = resize.height;
2041 this->ApplyAspectRatio();
2042}
2043
2044void NWidgetMatrix::AssignSizePosition(SizingType, int x, int y, uint given_width, uint given_height, bool)
2045{
2046 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
2047
2048 this->pos_x = x;
2049 this->pos_y = y;
2050 this->current_x = given_width;
2051 this->current_y = given_height;
2052
2053 /* Determine the size of the widgets, and the number of visible widgets on each of the axis. */
2054 this->widget_w = this->children.front()->smallest_x + this->pip_inter;
2055 this->widget_h = this->children.front()->smallest_y + this->pip_inter;
2056
2057 /* Account for the pip_inter is between widgets, so we need to account for that when
2058 * the division assumes pip_inter is used for all widgets. */
2059 this->widgets_x = CeilDiv(this->current_x - this->pip_pre - this->pip_post + this->pip_inter, this->widget_w);
2060 this->widgets_y = CeilDiv(this->current_y - this->pip_pre - this->pip_post + this->pip_inter, this->widget_h);
2061
2062 /* When resizing, update the scrollbar's count. E.g. with a vertical
2063 * scrollbar becoming wider or narrower means the amount of rows in
2064 * the scrollbar becomes respectively smaller or higher. */
2065 this->SetCount(this->count);
2066}
2067
2069{
2070 /* Falls outside of the matrix widget. */
2071 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
2072
2073 int start_x, start_y, base_offs_x, base_offs_y;
2074 this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
2075
2076 bool rtl = _current_text_dir == TD_RTL;
2077
2078 int widget_col = (rtl ?
2079 -x + (int)this->pip_post + this->pos_x + base_offs_x + this->widget_w - 1 - (int)this->pip_inter :
2080 x - (int)this->pip_pre - this->pos_x - base_offs_x
2081 ) / this->widget_w;
2082
2083 int widget_row = (y - base_offs_y - (int)this->pip_pre - this->pos_y) / this->widget_h;
2084
2085 this->current_element = (widget_row + start_y) * this->widgets_x + start_x + widget_col;
2086 if (this->current_element >= this->count) return nullptr;
2087
2088 NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->children.front().get());
2089 assert(child != nullptr);
2091 this->pos_x + (rtl ? this->pip_post - widget_col * this->widget_w : this->pip_pre + widget_col * this->widget_w) + base_offs_x,
2092 this->pos_y + this->pip_pre + widget_row * this->widget_h + base_offs_y,
2093 child->smallest_x, child->smallest_y, rtl);
2094
2095 return child->GetWidgetFromPos(x, y);
2096}
2097
2098/* virtual */ void NWidgetMatrix::Draw(const Window *w)
2099{
2100 /* Fill the background. */
2101 GfxFillRect(this->GetCurrentRect(), GetColourGradient(this->colour, Shade::Light));
2102
2103 /* Set up a clipping area for the previews. */
2104 bool rtl = _current_text_dir == TD_RTL;
2105 DrawPixelInfo tmp_dpi;
2106 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;
2107
2108 {
2109 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
2110
2111 /* Get the appropriate offsets so we can draw the right widgets. */
2112 NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->children.front().get());
2113 assert(child != nullptr);
2114 int start_x, start_y, base_offs_x, base_offs_y;
2115 this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
2116
2117 int offs_y = base_offs_y;
2118 for (int y = start_y; y < start_y + this->widgets_y + 1; y++, offs_y += this->widget_h) {
2119 /* Are we within bounds? */
2120 if (offs_y + child->smallest_y <= 0) continue;
2121 if (offs_y >= (int)this->current_y) break;
2122
2123 /* We've passed our amount of widgets. */
2124 if (y * this->widgets_x >= this->count) break;
2125
2126 int offs_x = base_offs_x;
2127 for (int x = start_x; x < start_x + this->widgets_x + 1; x++, offs_x += rtl ? -this->widget_w : this->widget_w) {
2128 /* Are we within bounds? */
2129 if (offs_x + child->smallest_x <= 0) continue;
2130 if (offs_x >= (int)this->current_x) continue;
2131
2132 /* Do we have this many widgets? */
2133 this->current_element = y * this->widgets_x + x;
2134 if (this->current_element >= this->count) break;
2135
2136 child->AssignSizePosition(SizingType::Resize, offs_x, offs_y, child->smallest_x, child->smallest_y, rtl);
2137 child->SetLowered(this->clicked == this->current_element);
2138 child->Draw(w);
2139 }
2140 }
2141 }
2142
2143 DrawOutline(w, this);
2144}
2145
2153void NWidgetMatrix::GetScrollOffsets(int &start_x, int &start_y, int &base_offs_x, int &base_offs_y)
2154{
2155 base_offs_x = _current_text_dir == TD_RTL ? this->widget_w * (this->widgets_x - 1) : 0;
2156 base_offs_y = 0;
2157 start_x = 0;
2158 start_y = 0;
2159 if (this->sb != nullptr) {
2160 if (this->sb->IsVertical()) {
2161 start_y = this->sb->GetPosition() / this->widget_h;
2162 base_offs_y += -this->sb->GetPosition() + start_y * this->widget_h;
2163 } else {
2164 start_x = this->sb->GetPosition() / this->widget_w;
2165 int sub_x = this->sb->GetPosition() - start_x * this->widget_w;
2166 if (_current_text_dir == TD_RTL) {
2167 base_offs_x += sub_x;
2168 } else {
2169 base_offs_x -= sub_x;
2170 }
2171 }
2172 }
2173}
2174
2184NWidgetBackground::NWidgetBackground(WidgetType tp, Colours colour, WidgetID index, std::unique_ptr<NWidgetPIPContainer> &&child) : NWidgetCore(tp, colour, index, 1, 1, {}, STR_NULL)
2185{
2186 assert(tp == WWT_PANEL || tp == WWT_INSET || tp == WWT_FRAME);
2187 this->child = std::move(child);
2188 if (this->child != nullptr) this->child->parent = this;
2190}
2191
2199void NWidgetBackground::Add(std::unique_ptr<NWidgetBase> &&nwid)
2200{
2201 if (this->child == nullptr) {
2202 this->child = std::make_unique<NWidgetVertical>();
2203 }
2204 nwid->parent = this->child.get();
2205 this->child->Add(std::move(nwid));
2206}
2207
2218void NWidgetBackground::SetPIP(uint8_t pip_pre, uint8_t pip_inter, uint8_t pip_post)
2219{
2220 if (this->child == nullptr) {
2221 this->child = std::make_unique<NWidgetVertical>();
2222 }
2223 this->child->parent = this;
2224 this->child->SetPIP(pip_pre, pip_inter, pip_post);
2225}
2226
2237void NWidgetBackground::SetPIPRatio(uint8_t pip_ratio_pre, uint8_t pip_ratio_inter, uint8_t pip_ratio_post)
2238{
2239 if (this->child == nullptr) {
2240 this->child = std::make_unique<NWidgetVertical>();
2241 }
2242 this->child->parent = this;
2243 this->child->SetPIPRatio(pip_ratio_pre, pip_ratio_inter, pip_ratio_post);
2244}
2245
2247{
2248 if (child != nullptr) child->AdjustPaddingForZoom();
2250}
2251
2253{
2254 if (this->child != nullptr) {
2255 this->child->SetupSmallestSize(w);
2256
2257 this->smallest_x = this->child->smallest_x;
2258 this->smallest_y = this->child->smallest_y;
2259 this->fill_x = this->child->fill_x;
2260 this->fill_y = this->child->fill_y;
2261 this->resize_x = this->child->resize_x;
2262 this->resize_y = this->child->resize_y;
2263
2264 /* Don't apply automatic padding if there is no child widget. */
2265 if (w == nullptr) return;
2266
2267 if (this->type == WWT_FRAME) {
2268 std::string text = GetStringForWidget(w, this);
2269 Dimension text_size = text.empty() ? Dimension{0, 0} : GetStringBoundingBox(text, this->text_size);
2270
2271 /* Account for the size of the frame's text if that exists */
2272 this->child->padding = WidgetDimensions::scaled.frametext;
2273 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);
2274
2275 this->smallest_x += this->child->padding.Horizontal();
2276 this->smallest_y += this->child->padding.Vertical();
2277
2278 this->smallest_x = std::max(this->smallest_x, text_size.width + WidgetDimensions::scaled.frametext.Horizontal());
2279 } else if (this->type == WWT_INSET) {
2280 /* Apply automatic padding for bevel thickness. */
2281 this->child->padding = WidgetDimensions::scaled.bevel;
2282
2283 this->smallest_x += this->child->padding.Horizontal();
2284 this->smallest_y += this->child->padding.Vertical();
2285 }
2286 this->ApplyAspectRatio();
2287 } else {
2288 Dimension d = {this->min_x, this->min_y};
2289 Dimension fill = {this->fill_x, this->fill_y};
2290 Dimension resize = {this->resize_x, this->resize_y};
2291 if (w != nullptr) { // A non-nullptr window pointer acts as switch to turn dynamic widget size on.
2292 if (this->type == WWT_FRAME || this->type == WWT_INSET) {
2293 std::string text = GetStringForWidget(w, this);
2294 if (!text.empty()) {
2295 Dimension background = GetStringBoundingBox(text, this->text_size);
2296 background.width += (this->type == WWT_FRAME) ? (WidgetDimensions::scaled.frametext.Horizontal()) : (WidgetDimensions::scaled.inset.Horizontal());
2297 d = maxdim(d, background);
2298 }
2299 }
2300 if (this->index >= 0) {
2302 switch (this->type) {
2303 default: NOT_REACHED();
2304 case WWT_PANEL: padding = {WidgetDimensions::scaled.framerect.Horizontal(), WidgetDimensions::scaled.framerect.Vertical()}; break;
2305 case WWT_FRAME: padding = {WidgetDimensions::scaled.frametext.Horizontal(), WidgetDimensions::scaled.frametext.Vertical()}; break;
2306 case WWT_INSET: padding = {WidgetDimensions::scaled.inset.Horizontal(), WidgetDimensions::scaled.inset.Vertical()}; break;
2307 }
2308 w->UpdateWidgetSize(this->index, d, padding, fill, resize);
2309 }
2310 }
2311 this->smallest_x = d.width;
2312 this->smallest_y = d.height;
2313 this->fill_x = fill.width;
2314 this->fill_y = fill.height;
2315 this->resize_x = resize.width;
2316 this->resize_y = resize.height;
2317 this->ApplyAspectRatio();
2318 }
2319}
2320
2321void NWidgetBackground::AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl)
2322{
2323 this->StoreSizePosition(sizing, x, y, given_width, given_height);
2324
2325 if (this->child != nullptr) {
2326 uint x_offset = (rtl ? this->child->padding.right : this->child->padding.left);
2327 uint width = given_width - this->child->padding.Horizontal();
2328 uint height = given_height - this->child->padding.Vertical();
2329 this->child->AssignSizePosition(sizing, x + x_offset, y + this->child->padding.top, width, height, rtl);
2330 }
2331}
2332
2334{
2335 this->NWidgetCore::FillWidgetLookup(widget_lookup);
2336 if (this->child != nullptr) this->child->FillWidgetLookup(widget_lookup);
2337}
2338
2340{
2341 if (this->current_x == 0 || this->current_y == 0) return;
2342
2343 Rect r = this->GetCurrentRect();
2344
2345 const DrawPixelInfo *dpi = _cur_dpi;
2346 if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2347
2348 switch (this->type) {
2349 case WWT_PANEL:
2351 break;
2352
2353 case WWT_FRAME:
2354 DrawFrame(r, this->colour, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
2355 break;
2356
2357 case WWT_INSET:
2358 DrawInset(r, this->colour, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
2359 break;
2360
2361 default:
2362 NOT_REACHED();
2363 }
2364
2365 if (this->index >= 0) w->DrawWidget(r, this->index);
2366 if (this->child != nullptr) this->child->Draw(w);
2367
2368 if (this->IsDisabled()) {
2370 }
2371
2372 DrawOutline(w, this);
2373}
2374
2376{
2377 NWidgetCore *nwid = nullptr;
2378 if (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) {
2379 if (this->child != nullptr) nwid = this->child->GetWidgetFromPos(x, y);
2380 if (nwid == nullptr) nwid = this;
2381 }
2382 return nwid;
2383}
2384
2386{
2387 NWidgetBase *nwid = nullptr;
2388 if (this->child != nullptr) nwid = this->child->GetWidgetOfType(tp);
2389 if (nwid == nullptr && this->type == tp) nwid = this;
2390 return nwid;
2391}
2392
2393NWidgetViewport::NWidgetViewport(WidgetID index) : NWidgetCore(NWID_VIEWPORT, Colours::Invalid, index, 1, 1, {}, STR_NULL)
2394{
2395}
2396
2398{
2399 this->smallest_x = this->min_x;
2400 this->smallest_y = this->min_y;
2401 this->ApplyAspectRatio();
2402}
2403
2405{
2406 if (this->current_x == 0 || this->current_y == 0) return;
2407
2410 _transparency_opt &= TransparencyOptions{TransparencyOption::Signs, TransparencyOption::Text}; // Disable all transparency, except textual stuff
2411 w->DrawViewport();
2412 } else {
2413 w->DrawViewport();
2414 }
2415
2416 /* Optionally shade the viewport. */
2417 if (this->disp_flags.Any({NWidgetDisplayFlag::ShadeGrey, NWidgetDisplayFlag::ShadeDimmed})) {
2419 }
2420
2421 DrawOutline(w, this);
2422}
2423
2430void NWidgetViewport::InitializeViewport(Window *w, std::variant<TileIndex, VehicleID> focus, ZoomLevel zoom)
2431{
2432 InitializeWindowViewport(w, this->pos_x, this->pos_y, this->current_x, this->current_y, focus, zoom);
2433}
2434
2440{
2441 if (w->viewport == nullptr) return;
2442
2443 Viewport &vp = *w->viewport;
2444 vp.left = w->left + this->pos_x;
2445 vp.top = w->top + this->pos_y;
2446 vp.width = this->current_x;
2447 vp.height = this->current_y;
2448
2449 vp.virtual_width = ScaleByZoom(vp.width, vp.zoom);
2451}
2452
2462Scrollbar::size_type Scrollbar::GetScrolledRowFromWidget(int clickpos, const Window * const w, WidgetID widget, int padding, int line_height) const
2463{
2464 int pos = w->GetRowFromWidget(clickpos, widget, padding, line_height);
2465 if (pos != INT_MAX) pos += this->GetPosition();
2466 return (pos < 0 || pos >= this->GetCount()) ? Scrollbar::npos : pos;
2467}
2468
2483EventState Scrollbar::UpdateListPositionOnKeyPress(int &list_position, uint16_t keycode) const
2484{
2485 int new_pos = list_position;
2486 switch (keycode) {
2487 case WKC_UP:
2488 /* scroll up by one */
2489 new_pos--;
2490 break;
2491
2492 case WKC_DOWN:
2493 /* scroll down by one */
2494 new_pos++;
2495 break;
2496
2497 case WKC_PAGEUP:
2498 /* scroll up a page */
2499 new_pos -= this->GetCapacity();
2500 break;
2501
2502 case WKC_PAGEDOWN:
2503 /* scroll down a page */
2504 new_pos += this->GetCapacity();
2505 break;
2506
2507 case WKC_HOME:
2508 /* jump to beginning */
2509 new_pos = 0;
2510 break;
2511
2512 case WKC_END:
2513 /* jump to end */
2514 new_pos = this->GetCount() - 1;
2515 break;
2516
2517 default:
2519 }
2520
2521 /* If there are no elements, there is nothing to scroll/update. */
2522 if (this->GetCount() != 0) {
2523 list_position = Clamp(new_pos, 0, this->GetCount() - 1);
2524 }
2525 return EventState::Handled;
2526}
2527
2528
2537{
2538 NWidgetBase *nwid = w->GetWidget<NWidgetBase>(widget);
2539 if (this->IsVertical()) {
2540 this->SetCapacity(((int)nwid->current_y - padding) / (int)nwid->resize_y);
2541 } else {
2542 this->SetCapacity(((int)nwid->current_x - padding) / (int)nwid->resize_x);
2543 }
2544}
2545
2553Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
2554{
2555 const int count = sb.GetCount() * resize_step;
2556 const int position = sb.GetPosition() * resize_step;
2557
2558 if (sb.IsVertical()) {
2559 r.top -= position;
2560 r.bottom = r.top + count;
2561 } else {
2562 bool rtl = _current_text_dir == TD_RTL;
2563 if (rtl) {
2564 r.right += position;
2565 r.left = r.right - count;
2566 } else {
2567 r.left -= position;
2568 r.right = r.left + count;
2569 }
2570 }
2571
2572 return r;
2573}
2574
2582{
2583 assert(tp == NWID_HSCROLLBAR || tp == NWID_VSCROLLBAR);
2584
2585 switch (this->type) {
2586 case NWID_HSCROLLBAR:
2587 this->SetResize(1, 0);
2588 this->SetFill(1, 0);
2589 this->SetToolTip(STR_TOOLTIP_HSCROLL_BAR_SCROLLS_LIST);
2590 break;
2591
2592 case NWID_VSCROLLBAR:
2593 this->SetResize(0, 1);
2594 this->SetFill(0, 1);
2595 this->SetToolTip(STR_TOOLTIP_VSCROLL_BAR_SCROLLS_LIST);
2596 break;
2597
2598 default: NOT_REACHED();
2599 }
2600}
2601
2603{
2604 this->min_x = 0;
2605 this->min_y = 0;
2606
2607 switch (this->type) {
2608 case NWID_HSCROLLBAR:
2609 this->SetMinimalSizeAbsolute(NWidgetScrollbar::GetHorizontalDimension().width * 3, NWidgetScrollbar::GetHorizontalDimension().height);
2610 break;
2611
2612 case NWID_VSCROLLBAR:
2613 this->SetMinimalSizeAbsolute(NWidgetScrollbar::GetVerticalDimension().width, NWidgetScrollbar::GetVerticalDimension().height * 3);
2614 break;
2615
2616 default: NOT_REACHED();
2617 }
2618
2619 this->smallest_x = this->min_x;
2620 this->smallest_y = this->min_y;
2621}
2622
2624{
2625 if (this->current_x == 0 || this->current_y == 0) return;
2626
2627 Rect r = this->GetCurrentRect();
2628
2629 const DrawPixelInfo *dpi = _cur_dpi;
2630 if (dpi->left > r.right || dpi->left + dpi->width <= r.left || dpi->top > r.bottom || dpi->top + dpi->height <= r.top) return;
2631
2632 bool up_lowered = this->disp_flags.Test(NWidgetDisplayFlag::ScrollbarUp);
2633 bool down_lowered = this->disp_flags.Test(NWidgetDisplayFlag::ScrollbarDown);
2634 bool middle_lowered = !this->disp_flags.Any({NWidgetDisplayFlag::ScrollbarUp, NWidgetDisplayFlag::ScrollbarDown}) && w->mouse_capture_widget == this->index;
2635
2636 if (this->type == NWID_HSCROLLBAR) {
2637 DrawHorizontalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2638 } else {
2639 DrawVerticalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
2640 }
2641
2642 if (this->IsDisabled()) {
2644 }
2645
2646 DrawOutline(w, this);
2647}
2648
2649/* static */ void NWidgetScrollbar::InvalidateDimensionCache()
2650{
2651 vertical_dimension.width = vertical_dimension.height = 0;
2652 horizontal_dimension.width = horizontal_dimension.height = 0;
2653}
2654
2655/* static */ Dimension NWidgetScrollbar::GetVerticalDimension()
2656{
2657 if (vertical_dimension.width == 0) {
2661 }
2662 return vertical_dimension;
2663}
2664
2665/* static */ Dimension NWidgetScrollbar::GetHorizontalDimension()
2666{
2667 if (horizontal_dimension.width == 0) {
2671 }
2672 return horizontal_dimension;
2673}
2674
2677
2680{
2681 shadebox_dimension.width = shadebox_dimension.height = 0;
2682 debugbox_dimension.width = debugbox_dimension.height = 0;
2683 defsizebox_dimension.width = defsizebox_dimension.height = 0;
2684 stickybox_dimension.width = stickybox_dimension.height = 0;
2685 resizebox_dimension.width = resizebox_dimension.height = 0;
2686 closebox_dimension.width = closebox_dimension.height = 0;
2687 dropdown_dimension.width = dropdown_dimension.height = 0;
2688}
2689
2697
2707{
2708 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);
2709 this->min_x = 0;
2710 this->min_y = 0;
2711 this->SetResize(0, 0);
2712
2713 switch (tp) {
2714 case WWT_EMPTY:
2715 if (colour != Colours::Invalid) [[unlikely]] throw std::runtime_error("WWT_EMPTY should not have a colour");
2716 break;
2717
2718 case WWT_TEXT:
2719 if (colour != Colours::Invalid) [[unlikely]] throw std::runtime_error("WWT_TEXT should not have a colour");
2720 this->SetFill(0, 0);
2722 break;
2723
2724 case WWT_LABEL:
2725 if (colour != Colours::Invalid) [[unlikely]] throw std::runtime_error("WWT_LABEL should not have a colour");
2726 [[fallthrough]];
2727
2728 case WWT_PUSHBTN:
2729 case WWT_IMGBTN:
2730 case WWT_PUSHIMGBTN:
2731 case WWT_IMGBTN_2:
2732 case WWT_TEXTBTN:
2733 case WWT_PUSHTXTBTN:
2734 case WWT_TEXTBTN_2:
2735 case WWT_IMGTEXTBTN:
2736 case WWT_PUSHIMGTEXTBTN:
2737 case WWT_BOOLBTN:
2738 case WWT_MATRIX:
2740 case NWID_PUSHBUTTON_DROPDOWN:
2741 this->SetFill(0, 0);
2742 break;
2743
2744 case WWT_ARROWBTN:
2745 case WWT_PUSHARROWBTN:
2746 this->SetFill(0, 0);
2747 this->SetAspect(WidgetDimensions::ASPECT_LEFT_RIGHT_BUTTON);
2748 break;
2749
2750 case WWT_EDITBOX:
2751 this->SetFill(0, 0);
2752 break;
2753
2754 case WWT_CAPTION:
2755 this->SetFill(1, 0);
2756 this->SetResize(1, 0);
2758 this->SetMinimalTextLines(1, WidgetDimensions::unscaled.captiontext.Vertical(), FontSize::Normal);
2759 this->SetToolTip(STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
2760 break;
2761
2762 case WWT_STICKYBOX:
2763 this->SetFill(0, 0);
2765 this->SetToolTip(STR_TOOLTIP_STICKY);
2766 this->SetAspect(this->uz_min_x, this->uz_min_y);
2767 break;
2768
2769 case WWT_SHADEBOX:
2770 this->SetFill(0, 0);
2772 this->SetToolTip(STR_TOOLTIP_SHADE);
2773 this->SetAspect(this->uz_min_x, this->uz_min_y);
2774 break;
2775
2776 case WWT_DEBUGBOX:
2777 this->SetFill(0, 0);
2779 this->SetToolTip(STR_TOOLTIP_DEBUG);
2780 this->SetAspect(this->uz_min_x, this->uz_min_y);
2781 break;
2782
2783 case WWT_DEFSIZEBOX:
2784 this->SetFill(0, 0);
2786 this->SetToolTip(STR_TOOLTIP_DEFSIZE);
2787 this->SetAspect(this->uz_min_x, this->uz_min_y);
2788 break;
2789
2790 case WWT_RESIZEBOX:
2791 this->SetFill(0, 0);
2794 this->SetToolTip(STR_TOOLTIP_RESIZE);
2795 break;
2796
2797 case WWT_CLOSEBOX:
2798 this->SetFill(0, 0);
2800 this->SetToolTip(STR_TOOLTIP_CLOSE_WINDOW);
2801 this->SetAspect(this->uz_min_x, this->uz_min_y);
2802 break;
2803
2804 case WWT_DROPDOWN:
2805 this->SetFill(0, 0);
2808 break;
2809
2810 default:
2811 NOT_REACHED();
2812 }
2813}
2814
2816{
2817 Dimension padding = {0, 0};
2818 Dimension size = {this->min_x, this->min_y};
2819 Dimension fill = {this->fill_x, this->fill_y};
2820 Dimension resize = {this->resize_x, this->resize_y};
2821 switch (this->type) {
2822 case WWT_EMPTY: {
2823 break;
2824 }
2825 case WWT_MATRIX: {
2826 padding = {WidgetDimensions::scaled.matrix.Horizontal(), WidgetDimensions::scaled.matrix.Vertical()};
2827 break;
2828 }
2829 case WWT_SHADEBOX: {
2830 padding = {WidgetDimensions::scaled.shadebox.Horizontal(), WidgetDimensions::scaled.shadebox.Vertical()};
2831 if (NWidgetLeaf::shadebox_dimension.width == 0) {
2835 }
2837 break;
2838 }
2839 case WWT_DEBUGBOX:
2840 if (_settings_client.gui.newgrf_developer_tools && w->IsNewGRFInspectable()) {
2841 padding = {WidgetDimensions::scaled.debugbox.Horizontal(), WidgetDimensions::scaled.debugbox.Vertical()};
2842 if (NWidgetLeaf::debugbox_dimension.width == 0) {
2846 }
2848 } else {
2849 /* If the setting is disabled we don't want to see it! */
2850 size.width = 0;
2851 fill.width = 0;
2852 resize.width = 0;
2853 }
2854 break;
2855
2856 case WWT_STICKYBOX: {
2857 padding = {WidgetDimensions::scaled.stickybox.Horizontal(), WidgetDimensions::scaled.stickybox.Vertical()};
2858 if (NWidgetLeaf::stickybox_dimension.width == 0) {
2862 }
2864 break;
2865 }
2866
2867 case WWT_DEFSIZEBOX: {
2868 padding = {WidgetDimensions::scaled.defsizebox.Horizontal(), WidgetDimensions::scaled.defsizebox.Vertical()};
2869 if (NWidgetLeaf::defsizebox_dimension.width == 0) {
2873 }
2875 break;
2876 }
2877
2878 case WWT_RESIZEBOX: {
2879 padding = {WidgetDimensions::scaled.resizebox.Horizontal(), WidgetDimensions::scaled.resizebox.Vertical()};
2880 if (NWidgetLeaf::resizebox_dimension.width == 0) {
2884 }
2886 break;
2887 }
2888 case WWT_EDITBOX: {
2889 Dimension sprite_size = GetScaledSpriteSize(_current_text_dir == TD_RTL ? SPR_IMG_DELETE_RIGHT : SPR_IMG_DELETE_LEFT);
2890 size.width = std::max(size.width, ScaleGUITrad(30) + sprite_size.width);
2891 size.height = std::max(sprite_size.height, GetStringBoundingBox("_").height + WidgetDimensions::scaled.framerect.Vertical());
2892 }
2893 [[fallthrough]];
2894 case WWT_PUSHBTN: {
2895 padding = {WidgetDimensions::scaled.frametext.Horizontal(), WidgetDimensions::scaled.framerect.Vertical()};
2896 break;
2897 }
2898
2899 case WWT_BOOLBTN:
2900 size.width = SETTING_BUTTON_WIDTH;
2901 size.height = SETTING_BUTTON_HEIGHT;
2902 break;
2903
2904 case WWT_IMGBTN:
2905 case WWT_IMGBTN_2:
2906 case WWT_PUSHIMGBTN: {
2907 padding = {WidgetDimensions::scaled.imgbtn.Horizontal(), WidgetDimensions::scaled.imgbtn.Vertical()};
2908 Dimension d2 = GetScaledSpriteSize(this->widget_data.sprite);
2909 if (this->type == WWT_IMGBTN_2) d2 = maxdim(d2, GetScaledSpriteSize(this->widget_data.sprite + 1));
2910 d2.width += padding.width;
2911 d2.height += padding.height;
2912 size = maxdim(size, d2);
2913 break;
2914 }
2915
2916 case WWT_IMGTEXTBTN:
2917 case WWT_PUSHIMGTEXTBTN: {
2918 padding = {WidgetDimensions::scaled.framerect.Horizontal(), WidgetDimensions::scaled.framerect.Vertical()};
2920 Dimension dt = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2921 Dimension d2{
2922 padding.width + 2 * (di.width + WidgetDimensions::scaled.hsep_wide) + dt.width,
2923 padding.height + std::max(di.height, dt.height)
2924 };
2925 size = maxdim(size, d2);
2926 break;
2927 }
2928
2929 case WWT_ARROWBTN:
2930 case WWT_PUSHARROWBTN: {
2931 padding = {WidgetDimensions::scaled.imgbtn.Horizontal(), WidgetDimensions::scaled.imgbtn.Vertical()};
2933 d2.width += padding.width;
2934 d2.height += padding.height;
2935 size = maxdim(size, d2);
2936 break;
2937 }
2938
2939 case WWT_CLOSEBOX: {
2940 padding = {WidgetDimensions::scaled.closebox.Horizontal(), WidgetDimensions::scaled.closebox.Vertical()};
2941 if (NWidgetLeaf::closebox_dimension.width == 0) {
2945 }
2947 break;
2948 }
2949 case WWT_TEXTBTN:
2950 case WWT_PUSHTXTBTN:
2951 case WWT_TEXTBTN_2: {
2952 padding = {WidgetDimensions::scaled.framerect.Horizontal(), WidgetDimensions::scaled.framerect.Vertical()};
2953 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2954 d2.width += padding.width;
2955 d2.height += padding.height;
2956 size = maxdim(size, d2);
2957 break;
2958 }
2959 case WWT_LABEL:
2960 case WWT_TEXT: {
2961 size = maxdim(size, GetStringBoundingBox(GetStringForWidget(w, this), this->text_size));
2962 break;
2963 }
2964 case WWT_CAPTION: {
2965 padding = {WidgetDimensions::scaled.captiontext.Horizontal(), WidgetDimensions::scaled.captiontext.Vertical()};
2966 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2967 d2.width += padding.width;
2968 d2.height += padding.height;
2969 size = maxdim(size, d2);
2970 break;
2971 }
2972 case WWT_DROPDOWN:
2974 case NWID_PUSHBUTTON_DROPDOWN: {
2975 if (NWidgetLeaf::dropdown_dimension.width == 0) {
2977 NWidgetLeaf::dropdown_dimension.width += WidgetDimensions::scaled.vscrollbar.Horizontal();
2978 NWidgetLeaf::dropdown_dimension.height += WidgetDimensions::scaled.vscrollbar.Vertical();
2979 }
2980 padding = {WidgetDimensions::scaled.dropdowntext.Horizontal() + NWidgetLeaf::dropdown_dimension.width + WidgetDimensions::scaled.fullbevel.Horizontal(), WidgetDimensions::scaled.dropdowntext.Vertical()};
2981 Dimension d2 = GetStringBoundingBox(GetStringForWidget(w, this), this->text_size);
2982 d2.width += padding.width;
2983 d2.height = std::max(d2.height + padding.height, NWidgetLeaf::dropdown_dimension.height);
2984 size = maxdim(size, d2);
2985 break;
2986 }
2987 default:
2988 NOT_REACHED();
2989 }
2990
2991 if (this->index >= 0) w->UpdateWidgetSize(this->index, size, padding, fill, resize);
2992
2993 this->smallest_x = size.width;
2994 this->smallest_y = size.height;
2995 this->fill_x = fill.width;
2996 this->fill_y = fill.height;
2997 this->resize_x = resize.width;
2998 this->resize_y = resize.height;
2999 this->ApplyAspectRatio();
3000}
3001
3003{
3004 if (this->current_x == 0 || this->current_y == 0) return;
3005
3006 /* Setup a clipping rectangle... for WWT_EMPTY or WWT_TEXT, an extra scaled pixel is allowed in case text shadow encroaches. */
3007 int extra = (this->type == WWT_EMPTY || this->type == WWT_TEXT) ? ScaleGUITrad(1) : 0;
3008 DrawPixelInfo new_dpi;
3009 if (!FillDrawPixelInfo(&new_dpi, this->pos_x, this->pos_y, this->current_x + extra, this->current_y + extra)) return;
3010 /* ...but keep coordinates relative to the window. */
3011 new_dpi.left += this->pos_x;
3012 new_dpi.top += this->pos_y;
3013
3014 AutoRestoreBackup dpi_backup(_cur_dpi, &new_dpi);
3015
3016 Rect r = this->GetCurrentRect();
3017
3018 bool clicked = this->IsLowered();
3019 switch (this->type) {
3020 case WWT_EMPTY:
3021 /* WWT_EMPTY used as a spacer indicates a potential design issue. */
3022 if (this->index == -1 && _draw_widget_outlines) {
3024 }
3025 break;
3026
3027 case WWT_PUSHBTN:
3028 DrawFrameRect(r, this->colour, clicked ? FrameFlag::Lowered : FrameFlags{});
3029 break;
3030
3031 case WWT_BOOLBTN: {
3033 Colours button_colour = this->widget_data.alternate_colour;
3034 if (button_colour == Colours::Invalid) button_colour = this->colour;
3035 DrawBoolButton(pt.x, pt.y, button_colour, this->colour, clicked, !this->IsDisabled());
3036 break;
3037 }
3038
3039 case WWT_IMGBTN:
3040 case WWT_PUSHIMGBTN:
3041 case WWT_IMGBTN_2:
3042 DrawImageButtons(r, this->type, this->colour, clicked, this->widget_data.sprite, this->align);
3043 break;
3044
3045 case WWT_TEXTBTN:
3046 case WWT_PUSHTXTBTN:
3047 case WWT_TEXTBTN_2:
3048 DrawFrameRect(r, this->colour, clicked ? FrameFlag::Lowered : FrameFlags{});
3049 DrawLabel(r, this->text_colour, GetStringForWidget(w, this, (type & WWT_MASK) == WWT_TEXTBTN_2 && clicked), this->align, this->text_size);
3050 break;
3051
3052 case WWT_IMGTEXTBTN:
3053 case WWT_PUSHIMGTEXTBTN:
3054 DrawImageTextButtons(r, this->colour, clicked, this->widget_data.sprite, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3055 break;
3056
3057 case WWT_ARROWBTN:
3058 case WWT_PUSHARROWBTN: {
3059 SpriteID sprite;
3060 switch (this->widget_data.arrow_widget_type) {
3063 case ArrowWidgetType::Left: sprite = SPR_ARROW_LEFT; break;
3064 case ArrowWidgetType::Right: sprite = SPR_ARROW_RIGHT; break;
3065 default: NOT_REACHED();
3066 }
3067 DrawImageButtons(r, WWT_PUSHIMGBTN, this->colour, clicked, sprite, this->align);
3068 break;
3069 }
3070
3071 case WWT_LABEL:
3072 DrawLabel(r, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3073 break;
3074
3075 case WWT_TEXT:
3076 DrawText(r, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3077 break;
3078
3079 case WWT_MATRIX:
3080 DrawMatrix(r, this->colour, clicked, this->widget_data.matrix.width, this->widget_data.matrix.height, this->resize_x, this->resize_y);
3081 break;
3082
3083 case WWT_EDITBOX: {
3084 const QueryString *query = w->GetQueryString(this->index);
3085 if (query != nullptr) query->DrawEditBox(w, this->index);
3086 break;
3087 }
3088
3089 case WWT_CAPTION:
3090 DrawCaption(r, this->colour, w->owner, this->text_colour, GetStringForWidget(w, this), this->align, this->text_size);
3091 break;
3092
3093 case WWT_SHADEBOX:
3094 DrawShadeBox(r, this->colour, w->IsShaded());
3095 break;
3096
3097 case WWT_DEBUGBOX:
3098 DrawDebugBox(r, this->colour, clicked);
3099 break;
3100
3101 case WWT_STICKYBOX:
3103 break;
3104
3105 case WWT_DEFSIZEBOX:
3106 DrawDefSizeBox(r, this->colour, clicked);
3107 break;
3108
3109 case WWT_RESIZEBOX:
3110 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 == ResizeWidgetType::ShowBevel);
3111 break;
3112
3113 case WWT_CLOSEBOX:
3114 DrawCloseBox(r, this->colour);
3115 break;
3116
3117 case WWT_DROPDOWN:
3118 DrawButtonDropdown(r, this->colour, false, clicked, GetStringForWidget(w, this), this->align);
3119 break;
3120
3122 case NWID_PUSHBUTTON_DROPDOWN:
3123 DrawButtonDropdown(r, this->colour, clicked, this->disp_flags.Test(NWidgetDisplayFlag::DropdownActive), GetStringForWidget(w, this), this->align);
3124 break;
3125
3126 default:
3127 NOT_REACHED();
3128 }
3129 if (this->index >= 0) w->DrawWidget(r, this->index);
3130
3131 if (this->IsDisabled() && this->type != WWT_BOOLBTN) {
3132 /* WWT_BOOLBTN is excluded as it draws its own disabled state. */
3134 }
3135
3136 DrawOutline(w, this);
3137}
3138
3147{
3148 if (_current_text_dir == TD_LTR) {
3149 int button_width = this->pos_x + this->current_x - NWidgetLeaf::dropdown_dimension.width;
3150 return pt.x < button_width;
3151 } else {
3152 int button_left = this->pos_x + NWidgetLeaf::dropdown_dimension.width;
3153 return pt.x >= button_left;
3154 }
3155}
3156
3157/* == Conversion code from NWidgetPart array to NWidgetBase* tree == */
3158
3165{
3166 return tp > WPT_ATTRIBUTE_BEGIN && tp < WPT_ATTRIBUTE_END;
3167}
3168
3176{
3177 switch (nwid.type) {
3178 case WPT_RESIZE: {
3179 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3180 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_RESIZE requires NWidgetResizeBase");
3181 assert(nwid.u.xy.x >= 0 && nwid.u.xy.y >= 0);
3182 nwrb->SetResize(nwid.u.xy.x, nwid.u.xy.y);
3183 break;
3184 }
3185
3186 case WPT_MINSIZE: {
3187 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3188 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_MINSIZE requires NWidgetResizeBase");
3189 assert(nwid.u.xy.x >= 0 && nwid.u.xy.y >= 0);
3190 nwrb->SetMinimalSize(nwid.u.xy.x, nwid.u.xy.y);
3191 break;
3192 }
3193
3194 case WPT_MINTEXTLINES: {
3195 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3196 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_MINTEXTLINES requires NWidgetResizeBase");
3197 assert(nwid.u.text_lines.size >= FontSize::Begin && nwid.u.text_lines.size < FontSize::End);
3199 break;
3200 }
3201
3202 case WPT_TOOLBARSIZE: {
3203 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3204 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_TOOLBARSIZE requires NWidgetResizeBase");
3205 assert(nwid.u.xy.x >= 0);
3206 nwrb->SetToolbarMinimalSize(nwid.u.xy.x);
3207 break;
3208 }
3209
3210 case WPT_TEXTSTYLE: {
3211 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3212 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_TEXTSTYLE requires NWidgetCore");
3213 nwc->SetTextStyle(nwid.u.text_style.colour, nwid.u.text_style.size);
3214 break;
3215 }
3216
3217 case WPT_ALIGNMENT: {
3218 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3219 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_ALIGNMENT requires NWidgetCore");
3220 nwc->SetAlignment(nwid.u.align.align);
3221 break;
3222 }
3223
3224 case WPT_FILL: {
3225 NWidgetResizeBase *nwrb = dynamic_cast<NWidgetResizeBase *>(dest);
3226 if (nwrb == nullptr) [[unlikely]] throw std::runtime_error("WPT_FILL requires NWidgetResizeBase");
3227 nwrb->SetFill(nwid.u.xy.x, nwid.u.xy.y);
3228 break;
3229 }
3230
3231 case WPT_DATATIP: {
3232 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3233 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_DATATIP requires NWidgetCore");
3234 nwc->widget_data = nwid.u.data_tip.data;
3235 nwc->SetToolTip(nwid.u.data_tip.tooltip);
3236 break;
3237 }
3238
3239 case WPT_PADDING:
3240 if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_PADDING requires NWidgetBase");
3241 dest->SetPadding(nwid.u.padding);
3242 break;
3243
3244 case WPT_PIPSPACE: {
3245 NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(dest);
3246 if (nwc != nullptr) nwc->SetPIP(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3247
3248 NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(dest);
3249 if (nwb != nullptr) nwb->SetPIP(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3250
3251 if (nwc == nullptr && nwb == nullptr) [[unlikely]] throw std::runtime_error("WPT_PIPSPACE requires NWidgetPIPContainer or NWidgetBackground");
3252 break;
3253 }
3254
3255 case WPT_PIPRATIO: {
3256 NWidgetPIPContainer *nwc = dynamic_cast<NWidgetPIPContainer *>(dest);
3257 if (nwc != nullptr) nwc->SetPIPRatio(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3258
3259 NWidgetBackground *nwb = dynamic_cast<NWidgetBackground *>(dest);
3260 if (nwb != nullptr) nwb->SetPIPRatio(nwid.u.pip.pre, nwid.u.pip.inter, nwid.u.pip.post);
3261
3262 if (nwc == nullptr && nwb == nullptr) [[unlikely]] throw std::runtime_error("WPT_PIPRATIO requires NWidgetPIPContainer or NWidgetBackground");
3263 break;
3264 }
3265
3266 case WPT_SCROLLBAR: {
3267 NWidgetCore *nwc = dynamic_cast<NWidgetCore *>(dest);
3268 if (nwc == nullptr) [[unlikely]] throw std::runtime_error("WPT_SCROLLBAR requires NWidgetCore");
3269 nwc->scrollbar_index = nwid.u.widget.index;
3270 break;
3271 }
3272
3273 case WPT_ASPECT: {
3274 if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_ASPECT requires NWidgetBase");
3275 dest->aspect_ratio = nwid.u.aspect.ratio;
3276 dest->aspect_flags = nwid.u.aspect.flags;
3277 break;
3278 }
3279
3280 default:
3281 NOT_REACHED();
3282 }
3283}
3284
3291static std::unique_ptr<NWidgetBase> MakeNWidget(const NWidgetPart &nwid)
3292{
3293 assert(!IsAttributeWidgetPartType(nwid.type));
3294 assert(nwid.type != WPT_ENDCONTAINER);
3295
3296 switch (nwid.type) {
3297 case NWID_SPACER: return std::make_unique<NWidgetSpacer>(0, 0);
3298
3299 case WWT_PANEL: [[fallthrough]];
3300 case WWT_INSET: [[fallthrough]];
3301 case WWT_FRAME: return std::make_unique<NWidgetBackground>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index);
3302
3303 case NWID_HORIZONTAL: return std::make_unique<NWidgetHorizontal>(nwid.u.container.flags, nwid.u.container.index);
3304 case NWID_HORIZONTAL_LTR: return std::make_unique<NWidgetHorizontalLTR>(nwid.u.container.flags, nwid.u.container.index);
3305 case NWID_VERTICAL: return std::make_unique<NWidgetVertical>(nwid.u.container.flags, nwid.u.container.index);
3306 case NWID_SELECTION: return std::make_unique<NWidgetStacked>(nwid.u.widget.index);
3307 case NWID_MATRIX: return std::make_unique<NWidgetMatrix>(nwid.u.widget.colour, nwid.u.widget.index);
3308 case NWID_VIEWPORT: return std::make_unique<NWidgetViewport>(nwid.u.widget.index);
3309 case NWID_LAYER: return std::make_unique<NWidgetLayer>(nwid.u.widget.index);
3310
3311 case NWID_HSCROLLBAR: [[fallthrough]];
3312 case NWID_VSCROLLBAR: return std::make_unique<NWidgetScrollbar>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index);
3313
3314 case WPT_FUNCTION: return nwid.u.func_ptr();
3315
3316 default:
3317 assert((nwid.type & WWT_MASK) < WWT_LAST || (nwid.type & WWT_MASK) == NWID_BUTTON_DROPDOWN);
3318 return std::make_unique<NWidgetLeaf>(nwid.type, nwid.u.widget.colour, nwid.u.widget.index, WidgetData{}, STR_NULL);
3319 }
3320}
3321
3335static 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)
3336{
3337 dest = nullptr;
3338
3339 if (IsAttributeWidgetPartType(nwid_begin->type)) [[unlikely]] throw std::runtime_error("Expected non-attribute NWidgetPart type");
3340 if (nwid_begin->type == WPT_ENDCONTAINER) return nwid_begin;
3341
3342 fill_dest = IsContainerWidgetType(nwid_begin->type);
3343 dest = MakeNWidget(*nwid_begin);
3344 if (dest == nullptr) return nwid_begin;
3345
3346 ++nwid_begin;
3347
3348 /* Once a widget is created, we're now looking for attributes. */
3349 while (nwid_begin != nwid_end && IsAttributeWidgetPartType(nwid_begin->type)) {
3350 ApplyNWidgetPartAttribute(*nwid_begin, dest.get());
3351 ++nwid_begin;
3352 }
3353
3354 return nwid_begin;
3355}
3356
3363{
3364 return tp == NWID_HORIZONTAL || tp == NWID_HORIZONTAL_LTR || tp == NWID_VERTICAL || tp == NWID_MATRIX
3365 || tp == WWT_PANEL || tp == WWT_FRAME || tp == WWT_INSET || tp == NWID_SELECTION || tp == NWID_LAYER;
3366}
3367
3375static 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)
3376{
3377 /* If *parent == nullptr, only the first widget is read and returned. Otherwise, *parent must point to either
3378 * a #NWidgetContainer or a #NWidgetBackground object, and parts are added as much as possible. */
3379 NWidgetContainer *nwid_cont = dynamic_cast<NWidgetContainer *>(parent.get());
3380 NWidgetBackground *nwid_parent = dynamic_cast<NWidgetBackground *>(parent.get());
3381 assert(parent == nullptr || (nwid_cont != nullptr && nwid_parent == nullptr) || (nwid_cont == nullptr && nwid_parent != nullptr));
3382
3383 while (nwid_begin != nwid_end) {
3384 std::unique_ptr<NWidgetBase> sub_widget = nullptr;
3385 bool fill_sub = false;
3386 nwid_begin = MakeNWidget(nwid_begin, nwid_end, sub_widget, fill_sub);
3387
3388 /* Break out of loop when end reached */
3389 if (sub_widget == nullptr) break;
3390
3391 /* If sub-widget is a container, recursively fill that container. */
3392 if (fill_sub && IsContainerWidgetType(sub_widget->type)) {
3393 nwid_begin = MakeWidgetTree(nwid_begin, nwid_end, sub_widget);
3394 }
3395
3396 /* Add sub_widget to parent container if available, otherwise return the widget to the caller. */
3397 if (nwid_cont != nullptr) nwid_cont->Add(std::move(sub_widget));
3398 if (nwid_parent != nullptr) nwid_parent->Add(std::move(sub_widget));
3399 if (nwid_cont == nullptr && nwid_parent == nullptr) {
3400 parent = std::move(sub_widget);
3401 return nwid_begin;
3402 }
3403 }
3404
3405 if (nwid_begin == nwid_end) return nwid_begin; // Reached the end of the array of parts?
3406
3407 assert(nwid_begin < nwid_end);
3408 assert(nwid_begin->type == WPT_ENDCONTAINER);
3409 return std::next(nwid_begin); // *nwid_begin is also 'used'
3410}
3411
3419std::unique_ptr<NWidgetBase> MakeNWidgets(std::span<const NWidgetPart> nwid_parts, std::unique_ptr<NWidgetBase> &&container)
3420{
3421 if (container == nullptr) container = std::make_unique<NWidgetVertical>();
3422 [[maybe_unused]] auto nwid_part = MakeWidgetTree(std::begin(nwid_parts), std::end(nwid_parts), container);
3423#ifdef WITH_ASSERT
3424 if (nwid_part != std::end(nwid_parts)) [[unlikely]] throw std::runtime_error("Did not consume all NWidgetParts");
3425#endif
3426 return std::move(container);
3427}
3428
3438std::unique_ptr<NWidgetBase> MakeWindowNWidgetTree(std::span<const NWidgetPart> nwid_parts, NWidgetStacked **shade_select)
3439{
3440 auto nwid_begin = std::begin(nwid_parts);
3441 auto nwid_end = std::end(nwid_parts);
3442
3443 *shade_select = nullptr;
3444
3445 /* Read the first widget recursively from the array. */
3446 std::unique_ptr<NWidgetBase> nwid = nullptr;
3447 nwid_begin = MakeWidgetTree(nwid_begin, nwid_end, nwid);
3448 assert(nwid != nullptr);
3449
3450 NWidgetHorizontal *hor_cont = dynamic_cast<NWidgetHorizontal *>(nwid.get());
3451
3452 auto root = std::make_unique<NWidgetVertical>();
3453 root->Add(std::move(nwid));
3454 if (nwid_begin == nwid_end) return root; // There is no body at all.
3455
3456 if (hor_cont != nullptr && hor_cont->GetWidgetOfType(WWT_CAPTION) != nullptr && hor_cont->GetWidgetOfType(WWT_SHADEBOX) != nullptr) {
3457 /* If the first widget has a title bar and a shade box, silently add a shade selection widget in the tree. */
3458 auto shade_stack = std::make_unique<NWidgetStacked>(INVALID_WIDGET);
3459 *shade_select = shade_stack.get();
3460 /* Load the remaining parts into the shade stack. */
3461 shade_stack->Add(MakeNWidgets({nwid_begin, nwid_end}, std::make_unique<NWidgetVertical>()));
3462 root->Add(std::move(shade_stack));
3463 return root;
3464 }
3465
3466 /* Load the remaining parts into 'root'. */
3467 return MakeNWidgets({nwid_begin, nwid_end}, std::move(root));
3468}
3469
3480std::unique_ptr<NWidgetBase> MakeCompanyButtonRows(WidgetID widget_first, WidgetID widget_last, Colours button_colour, int max_length, StringID button_tooltip, bool resizable)
3481{
3482 assert(max_length >= 1);
3483 std::unique_ptr<NWidgetVertical> vert = nullptr; // Storage for all rows.
3484 std::unique_ptr<NWidgetHorizontal> hor = nullptr; // Storage for buttons in one row.
3485 int hor_length = 0;
3486
3488 sprite_size.width += WidgetDimensions::unscaled.matrix.Horizontal();
3489 sprite_size.height += WidgetDimensions::unscaled.matrix.Vertical();
3490
3491 for (WidgetID widnum = widget_first; widnum <= widget_last; widnum++) {
3492 /* Ensure there is room in 'hor' for another button. */
3493 if (hor_length == max_length) {
3494 if (vert == nullptr) vert = std::make_unique<NWidgetVertical>();
3495 vert->Add(std::move(hor));
3496 hor = nullptr;
3497 hor_length = 0;
3498 }
3499 if (hor == nullptr) {
3500 hor = std::make_unique<NWidgetHorizontal>();
3501 hor_length = 0;
3502 }
3503
3504 auto panel = std::make_unique<NWidgetBackground>(WWT_PANEL, button_colour, widnum);
3505 panel->SetMinimalSize(sprite_size.width, sprite_size.height);
3506 panel->SetFill(1, 1);
3507 if (resizable) panel->SetResize(1, 0);
3508 panel->SetToolTip(button_tooltip);
3509 hor->Add(std::move(panel));
3510 hor_length++;
3511 }
3512 if (vert == nullptr) return hor; // All buttons fit in a single row.
3513
3514 if (hor_length > 0 && hor_length < max_length) {
3515 /* Last row is partial, add a spacer at the end to force all buttons to the left. */
3516 auto spc = std::make_unique<NWidgetSpacer>(sprite_size.width, sprite_size.height);
3517 spc->SetFill(1, 1);
3518 if (resizable) spc->SetResize(1, 0);
3519 hor->Add(std::move(spc));
3520 }
3521 if (hor != nullptr) vert->Add(std::move(hor));
3522 return vert;
3523}
3524
3531{
3532 assert(parent_window != nullptr);
3533 if (parent_window->nested_focus != nullptr) {
3534 for (auto &widget : this->children) {
3535 if (parent_window->nested_focus == widget.get()) {
3536 parent_window->UnfocusFocusedWidget();
3537 }
3538 }
3539 }
3540}
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:2375
NWidgetBase * GetWidgetOfType(WidgetType tp) override
Retrieve a widget by its type.
Definition widget.cpp:2385
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:2321
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2252
void AdjustPaddingForZoom() override
Adjust the padding based on the user interface zoom.
Definition widget.cpp:2246
NWidgetBackground(WidgetType tp, Colours colour, WidgetID index, std::unique_ptr< NWidgetPIPContainer > &&child=nullptr)
Constructor parent nested widgets.
Definition widget.cpp:2184
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:2218
std::unique_ptr< NWidgetPIPContainer > child
Child widget.
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2339
void Add(std::unique_ptr< NWidgetBase > &&nwid)
Add a child to the parent.
Definition widget.cpp:2199
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:2333
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:2237
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:994
virtual void SetDirty(const Window *w) const
Mark the widget as 'dirty' (in need of repaint).
Definition widget.cpp:955
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:975
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:939
Baseclass for container widgets.
void Add(std::unique_ptr< NWidgetBase > &&wid)
Append widget wid to container.
Definition widget.cpp:1328
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1335
void UnfocusWidgets(Window *parent_window)
Unfocuses the focused widget of the window, if the focused widget is contained inside the container.
Definition widget.cpp:3530
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1343
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:1352
void AdjustPaddingForZoom() override
Adjust the padding based on the user interface zoom.
Definition widget.cpp:1316
NWidgetBase * GetWidgetOfType(WidgetType tp) override
Retrieve a widget by its type.
Definition widget.cpp:1306
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:1260
bool IsDisabled() const
Return whether the widget is disabled.
void SetSprite(SpriteID sprite)
Set sprite of the nested widget.
Definition widget.cpp:1210
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:1250
void SetAlignment(Alignment align)
Set the text/image alignment of the nested widget.
Definition widget.cpp:1278
WidgetID GetScrollbarIndex() const
Get the WidgetID of this nested widget's scrollbar.
Definition widget.cpp:1296
void SetResizeWidgetType(ResizeWidgetType type)
Set the resize widget type of the nested widget.
Definition widget.cpp:1240
Alignment align
Alignment of text/image within widget.
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:1178
void SetSpriteTip(SpriteID sprite, StringID tool_tip)
Set sprite and tool tip of the nested widget.
Definition widget.cpp:1220
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:1287
WidgetID scrollbar_index
Index of an attached scrollbar.
StringID GetToolTip() const
Get the tool tip of the nested widget.
Definition widget.cpp:1269
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1301
void SetString(StringID string)
Set string of the nested widget.
Definition widget.cpp:1190
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:1231
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:1200
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:1749
Horizontal container.
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1571
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:1640
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:1499
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1477
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1517
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:3002
static void InvalidateDimensionCache()
Reset the cached dimensions.
Definition widget.cpp:2679
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:2706
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:2815
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:3146
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2098
int count
Amount of valid elements.
void SetClicked(int clicked)
Sets the clicked element in the matrix.
Definition widget.cpp:1967
int GetCurrentElement() const
Get current element.
Definition widget.cpp:2017
Scrollbar * sb
The scrollbar we're associated with.
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2022
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:2008
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:2153
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:2044
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:2068
int clicked
The currently clicked element.
void SetCount(int count)
Set the number of elements in this matrix.
Definition widget.cpp:1984
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:1564
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:1527
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:1544
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:1053
bool UpdateSize(uint min_x, uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1142
void AdjustPaddingForZoom() override
Adjust the padding based on the user interface zoom.
Definition widget.cpp:1035
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:1006
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:1098
void SetToolbarMinimalSize(uint8_t toolbar_size)
Set minimal size of the widget in toolbar-icon-relative width.
Definition widget.cpp:1063
void SetMinimalTextLines(uint8_t min_lines, uint8_t spacing, FontSize size)
Set minimal text lines for the widget.
Definition widget.cpp:1086
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:1122
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:1163
bool UpdateVerticalSize(uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1156
void SetResize(uint resize_x, uint resize_y)
Set resize step of the widget.
Definition widget.cpp:1109
uint8_t toolbar_size
Minimal size in terms of toolbar images.
void SetAspect(float ratio, AspectFlags flags=AspectFlag::ResizeX)
Set desired aspect ratio of this widget.
Definition widget.cpp:1017
void SetMinimalSizeAbsolute(uint min_x, uint min_y)
Set absolute (post-scaling) minimal size of the widget.
Definition widget.cpp:1073
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:2623
NWidgetScrollbar(WidgetType tp, Colours colour, WidgetID index)
Scrollbar widget.
Definition widget.cpp:2581
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2602
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:1928
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1958
void SetDirty(const Window *w) const override
Mark the widget as 'dirty' (in need of repaint).
Definition widget.cpp:1953
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1941
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1934
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:1363
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:1404
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:1434
bool SetDisplayedPlane(int plane)
Select which plane to show (for NWID_SELECTION only).
Definition widget.cpp:1458
void FillWidgetLookup(WidgetLookup &widget_lookup) override
Fill the Window::widget_lookup with pointers to nested widgets in the tree.
Definition widget.cpp:1424
NWidgetCore * GetWidgetFromPos(int x, int y) override
Retrieve a widget by its position.
Definition widget.cpp:1443
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:1823
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:1754
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:2439
void Draw(const Window *w) override
Draw the widgets of the tree.
Definition widget.cpp:2404
void SetupSmallestSize(Window *w) override
Compute smallest size needed by the widget.
Definition widget.cpp:2397
void InitializeViewport(Window *w, std::variant< TileIndex, VehicleID > focus, ZoomLevel zoom)
Initialize the viewport of the window.
Definition widget.cpp:2430
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, Scrollbar::Stepping unit=Stepping::Small)
Updates the position of the first visible element by the given amount.
@ Big
Step in cap units.
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:2462
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:2536
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:2483
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.
static constexpr uint WD_CAPTION_HEIGHT
Minimum height of a title bar.
Definition window_gui.h:89
static constexpr uint WD_DROPDOWN_HEIGHT
Minimum height of a drop down widget.
Definition window_gui.h:90
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:39
RectPadding vscrollbar
Padding inside vertical scrollbar buttons.
Definition window_gui.h:38
static constexpr uint WD_CLOSEBOX_WIDTH
Minimum width of a close box widget.
Definition window_gui.h:88
static const WidgetDimensions unscaled
Unscaled widget dimensions.
Definition window_gui.h:95
static constexpr uint WD_RESIZEBOX_WIDTH
Minimum width of a resize box widget.
Definition window_gui.h:87
static constexpr uint WD_STICKYBOX_WIDTH
Minimum width of a standard sticky box widget.
Definition window_gui.h:84
static constexpr uint WD_DEBUGBOX_WIDTH
Minimum width of a standard debug box widget.
Definition window_gui.h:85
static constexpr uint WD_DEFSIZEBOX_WIDTH
Minimum width of a standard defsize box widget.
Definition window_gui.h:86
static constexpr uint WD_SHADEBOX_WIDTH
Distances used in drawing widgets.
Definition window_gui.h:83
TypedIndexContainer< std::array< Colours, MAX_COMPANIES >, CompanyID > _company_colours
NOSAVE: can be determined from company structs.
Functions related to companies.
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23).
Definition enum_type.hpp:21
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Definition fontcache.cpp:88
Dimension maxdim(const Dimension &d1, const Dimension &d2)
Compute bounding box of both dimensions.
Geometry functions.
@ ForceRight
Force align to the right.
@ Centre
Align to the centre.
@ Start
Align to the start, LTR/RTL aware.
@ ForceLeft
Force align to the left.
int CentreBounds(int min, int max, int size)
Determine where to position a centred object.
@ Bottom
Align to the bottom.
@ Top
Align to the top.
@ Middle
Align to the middle.
int GetStringHeight(std::string_view str, int maxw, FontSize fontsize)
Calculates height of string (in pixels).
Definition gfx.cpp:716
Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
Get the size of a sprite.
Definition gfx.cpp:971
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
Definition gfx.cpp:899
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:1037
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
int DrawString(int left, int right, int top, std::string_view str, ExtendedTextColour colour, Alignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
Definition gfx.cpp:668
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:1572
Dimension GetSquareScaledSpriteSize(SpriteID sprid)
Scale sprite size for GUI, as a square.
Definition widget.cpp:85
Dimension GetScaledSpriteSize(SpriteID sprid)
Scale sprite size for GUI.
Definition widget.cpp:70
void DrawSpriteIgnorePadding(SpriteID img, PaletteID pal, const Rect &r, Alignment align)
Draw a sprite within a Rect, ignoring the sprite's padding.
Definition widget.cpp:350
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
@ Begin
Marker for the first font in the enumeration.
Definition gfx_type.h:255
@ End
Marker for the end of the enumerations.
Definition gfx_type.h:254
@ Normal
Index of the normal font in the font tables.
Definition gfx_type.h:249
uint32_t PaletteID
The number of the palette.
Definition gfx_type.h:18
Colours
One of 16 base colours used for companies and windows/widgets.
Definition gfx_type.h:283
@ White
White.
Definition gfx_type.h:300
@ Invalid
Invalid marker.
Definition gfx_type.h:302
@ End
End-of-array marker.
Definition gfx_type.h:301
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:315
@ White
White colour.
Definition gfx_type.h:330
@ Black
Black colour.
Definition gfx_type.h:334
@ Silver
Silver colour.
Definition gfx_type.h:319
@ Recolour
Apply a recolour sprite to the screen content.
Definition gfx_type.h:394
@ Checker
Draw only every second pixel, used for greying-out.
Definition gfx_type.h:393
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:3438
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:3419
constexpr NWidgetPart SetToolTip(StringID tip)
Widget part function for setting tooltip and clearing the widget data.
constexpr NWidgetPart SetAlignment(Alignment 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:972
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:1520
#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, Shade shade)
Get colour gradient palette index.
Definition palette.cpp:393
@ Darker
Darker colour shade.
@ Lightest
Lightest colour shade.
@ Lighter
Lighter colour shade.
@ Normal
Normal colour shade.
@ Light
Light colour shade.
@ Dark
Dark colour shade.
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.
@ Invalid
broken savegame (used internally)
Definition saveload.h:449
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 const SpriteID SPR_ARROW_RIGHT
Definition sprites.h:87
static const SpriteID SPR_WINDOW_SHADE
Shade the window icon.
Definition sprites.h:78
static const SpriteID SPR_WINDOW_DEBUG
NewGRF debug window icon.
Definition sprites.h:80
static const SpriteID SPR_PIN_UP
Pin icon.
Definition sprites.h:91
static const SpriteID SPR_WINDOW_UNSHADE
Unshade the window icon.
Definition sprites.h:79
static const SpriteID SPR_COMPANY_ICON
Icon showing company colour.
Definition sprites.h:380
static const SpriteID SPR_ARROW_LEFT
Definition sprites.h:86
static const SpriteID SPR_WINDOW_RESIZE_RIGHT
Resize icon to the right.
Definition sprites.h:76
static const SpriteID SPR_WINDOW_RESIZE_LEFT
Resize icon to the left.
Definition sprites.h:77
static constexpr uint8_t PALETTE_TEXT_RECOLOUR
Set if palette is actually a magic text recolour.
Definition sprites.h:1713
static const SpriteID SPR_ARROW_DOWN
Definition sprites.h:84
static const SpriteID SPR_ARROW_UP
Definition sprites.h:85
static const PaletteID PALETTE_TO_TRANSPARENT
This sets the sprite to transparent.
Definition sprites.h:1785
static const PaletteID PALETTE_NEWSPAPER
Recolour sprite for newspaper-greying.
Definition sprites.h:1787
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.
Horizontal and vertical alignment.
AlignmentH ResolveRTL() const
Resolve horizontal alignment for the current text direction.
AlignmentV v
Vertical alignment.
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
Alignment 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:307
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 coordinates.
Rect WithX(int new_left, int new_right) const
Create a new Rect, replacing the left and right coordinates.
Rect Expand(int s) const
Copy and expand Rect by s pixels.
Data structure for viewport, display of a part of the world.
int top
Screen coordinate top edge of the viewport.
int width
Screen width of the viewport.
ZoomLevel zoom
The zoom level of the viewport.
int virtual_width
width << zoom
int left
Screen coordinate left edge of the viewport.
int height
Screen height of the viewport.
int virtual_height
height << zoom
Container with the data associated to a single widget.
Data structure for an opened window.
Definition window_gui.h:273
virtual void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize)
Update size and resize step of a widget in the window.
Definition window_gui.h:623
static int SortButtonWidth()
Get width of up/down arrow of sort button state.
Definition widget.cpp:839
void DrawWidgets() const
Paint all widgets of a window.
Definition widget.cpp:792
std::unique_ptr< ViewportData > viewport
Pointer to viewport data, if present.
Definition window_gui.h:318
virtual std::string GetWidgetString(WidgetID widget, StringID stringid) const
Get the raw string for a widget.
Definition window.cpp:510
WidgetID mouse_capture_widget
ID of current mouse capture widget (e.g. dragged scrollbar). INVALID_WIDGET if no widget has mouse ca...
Definition window_gui.h:326
virtual void OnScrollbarScroll(WidgetID widget)
Notify window that a scrollbar position has been updated.
Definition window_gui.h:723
void UnfocusFocusedWidget()
Makes no widget on this window have focus.
Definition window.cpp:475
void DrawSortButton(WidgetID widget, bool descending) const
Draw a sort button's up or down arrow symbol.
Definition widget.cpp:824
virtual bool IsNewGRFInspectable() const
Is the data related to this window NewGRF inspectable?
Definition window_gui.h:866
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:609
Owner owner
The owner of the content shown in this window. Company colour is acquired from this variable.
Definition window_gui.h:316
int left
x position of left edge of the window
Definition window_gui.h:309
bool IsShaded() const
Is window shaded currently?
Definition window_gui.h:562
const NWidgetCore * nested_focus
Currently focused nested widget, or nullptr if no nested widget has focus.
Definition window_gui.h:319
int top
y position of top edge of the window
Definition window_gui.h:310
const QueryString * GetQueryString(WidgetID widnum) const
Return the querystring associated to a editbox.
Definition window.cpp:339
WidgetLookup widget_lookup
Indexed access to the nested widget tree. Do not access directly, use Window::GetWidget() instead.
Definition window_gui.h:322
int GetRowFromWidget(int clickpos, WidgetID widget, int padding, int line_height=-1) const
Compute the row of a widget that a user clicked in.
Definition window.cpp:215
const NWID * GetWidget(WidgetID widnum) const
Get the nested widget with number widnum from the nested widget tree.
Definition window_gui.h:989
WindowFlags flags
Window flags.
Definition window_gui.h:300
std::unique_ptr< NWidgetBase > nested_root
Root of the nested tree.
Definition window_gui.h:321
int height
Height of the window (number of pixels down in y direction).
Definition window_gui.h:312
int width
width of the window (number of pixels to the right in x direction)
Definition window_gui.h:311
Functions related to transparency.
EnumBitSet< TransparencyOption, uint32_t > TransparencyOptions
Bitset of TransparencyOption elements.
TransparencyOptions _transparency_opt
The bits that should be transparent.
@ Text
loading and cost/income text
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 DrawButtonDropdown(const Rect &r, Colours colour, bool clicked_button, bool clicked_dropdown, std::string_view str, Alignment align)
Draw a button with a dropdown (WWT_DROPDOWN and NWID_BUTTON_DROPDOWN).
Definition widget.cpp:774
static void DrawCloseBox(const Rect &r, Colours colour)
Draw a close box.
Definition widget.cpp:723
void DrawFrameRect(int left, int top, int right, int bottom, Colours colour, FrameFlags flags)
Draw frame rectangle.
Definition widget.cpp:308
static Point GetAlignedPosition(const Rect &r, const Dimension &d, Alignment align)
Calculate x and y coordinates for an aligned object within a window.
Definition widget.cpp:140
Rect ScrollRect(Rect r, const Scrollbar &sb, int resize_step)
Apply 'scroll' to a rect to be drawn in.
Definition widget.cpp:2553
void ApplyNWidgetPartAttribute(const NWidgetPart &nwid, NWidgetBase *dest)
Apply an attribute NWidgetPart to an NWidget.
Definition widget.cpp:3175
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:168
static void DrawDefSizeBox(const Rect &r, Colours colour, bool clicked)
Draw a defsize box.
Definition widget.cpp:684
Dimension GetSquareScaledSpriteSize(SpriteID sprid)
Scale sprite size for GUI, as a square.
Definition widget.cpp:85
bool IsContainerWidgetType(WidgetType tp)
Test if WidgetType is a container widget.
Definition widget.cpp:3362
static void DrawDebugBox(const Rect &r, Colours colour, bool clicked)
Draw a NewGRF debug box.
Definition widget.cpp:695
static void DrawStickyBox(const Rect &r, Colours colour, bool clicked)
Draw a sticky box.
Definition widget.cpp:673
static Dimension _toolbar_image_size
Cached dimension of maximal toolbar sprite size.
Definition widget.cpp:92
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:469
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:568
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:3291
static void DrawText(const Rect &r, TextColour colour, std::string_view str, Alignment align, FontSize fs)
Draw text.
Definition widget.cpp:435
void SetupWidgetDimensions()
Set up pre-scaled versions of Widget Dimensions.
Definition widget.cpp:98
static void DrawShadeBox(const Rect &r, Colours colour, bool clicked)
Draw a shade box.
Definition widget.cpp:662
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:293
Dimension GetToolbarMaximalImageSize()
Get maximal square size of a toolbar image.
static void DrawFrame(const Rect &r, Colours colour, TextColour text_colour, std::string_view str, Alignment align, FontSize fs)
Draw a frame widget.
Definition widget.cpp:609
static void DrawImageTextButtons(const Rect &r, Colours colour, bool clicked, SpriteID img, TextColour text_colour, const std::string &text, Alignment align, FontSize fs)
Draw a button with image and rext.
Definition widget.cpp:390
static bool IsAttributeWidgetPartType(WidgetType tp)
Test if (an NWidgetPart) WidgetType is an attribute widget part type.
Definition widget.cpp:3164
static void DrawInset(const Rect &r, Colours colour, TextColour text_colour, std::string_view str, Alignment align, FontSize fs)
Draw an inset widget.
Definition widget.cpp:453
void ScrollbarClickHandler(Window *w, NWidgetCore *nw, int x, int y)
Special handling for the scrollbar widget type.
Definition widget.cpp:269
static void DrawImageButtons(const Rect &r, WidgetType type, Colours colour, bool clicked, SpriteID img, Alignment align)
Draw an image button.
Definition widget.cpp:370
static void DrawResizeBox(const Rect &r, Colours colour, bool at_left, bool clicked, bool bevel)
Draw a resize box.
Definition widget.cpp:708
void DrawCaption(const Rect &r, Colours colour, Owner owner, TextColour text_colour, std::string_view str, Alignment align, FontSize fs)
Draw a caption bar.
Definition widget.cpp:744
void DrawSpriteIgnorePadding(SpriteID img, PaletteID pal, const Rect &r, Alignment align)
Draw a sprite within a Rect, ignoring the sprite's padding.
Definition widget.cpp:350
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:204
static void DrawLabel(const Rect &r, TextColour colour, std::string_view str, Alignment align, FontSize fs)
Draw the label-part of a widget.
Definition widget.cpp:418
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:3375
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:527
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:3480
EnumBitSet< AspectFlag, uint8_t > AspectFlags
Bitset of AspectFlag elements.
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
@ WPT_TOOLBARSIZE
Widget part for specifying minimal size in terms of toolbar images.
Definition widget_type.h:93
@ 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:94
@ NWID_HSCROLLBAR
Horizontal scrollbar.
Definition widget_type.h:75
@ WPT_ENDCONTAINER
Widget part to denote end of a container.
Definition widget_type.h:97
@ WWT_RESIZEBOX
Resize box (normally at bottom-right of a window).
Definition widget_type.h: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:96
@ 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.
@ NoTransparency
Viewport is never transparent.
@ ShadeDimmed
Display dimmed colours in the viewport.
@ DropdownActive
Dropdown menu of the button dropdown widget is active.
@ ScrollbarUp
Up-button is lowered bit.
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().
@ Resize
Resize the nested widget tree.
@ Smallest
Initialize nested widget tree to smallest size. Also updates current_x and current_y.
@ Right
Force the arrow to the right.
Definition widget_type.h:23
@ Left
Force the arrow to the left.
Definition widget_type.h:22
@ Decrease
Arrow to the left or in case of RTL to the right.
Definition widget_type.h:20
@ 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.
ResizeWidgetType
WidgetData values for a resize box widget.
Definition widget_type.h:27
@ ShowBevel
Bevel of resize box is shown.
Definition widget_type.h:28
@ ResizeY
Resize vertically to reach desired aspect ratio.
@ ResizeX
Resize horizontally to reach desired aspect ratio.
bool _window_highlight_colour
If false, highlight is white, otherwise the by the widget defined colour.
Definition window.cpp:70
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:308
EnumBitSet< FrameFlag, uint8_t > FrameFlags
Bitset of FrameFlag elements.
Definition window_gui.h:32
@ 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:228
@ Highlighted
Window has a widget that has a highlight.
Definition window_gui.h:233
@ SizingRight
Window is being resized towards the right.
Definition window_gui.h:227
@ WhiteBorder
Window white border counter bit mask.
Definition window_gui.h:232
@ Sticky
Window is made sticky by user.
Definition window_gui.h:230
int WidgetID
Widget ID.
Definition window_type.h:21
EventState
State of handling an event.
@ Handled
The passed event is handled.
@ NotHandled
The passed event is not handled.
static constexpr WidgetID INVALID_WIDGET
An invalid widget index.
Definition window_type.h:24
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