From d43ff8dc49e147bd6a9fb6f72a30ca87ff6d751d Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Wed, 27 Mar 2024 09:02:40 +0000 Subject: [PATCH] Change: Ability to set aspect ratio of a widget. This allows setting the shape of a widget without dealing with absolute pixel sizes. --- src/network/network_gui.cpp | 1 + src/smallmap_gui.cpp | 1 + src/widget.cpp | 61 +++++++++++++++++++++++++++++++++++++ src/widget_type.h | 32 +++++++++++++++++++ 4 files changed, 95 insertions(+) diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 54584a1a51..084dac2b70 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -127,6 +127,7 @@ public: } this->smallest_x = this->children.front()->smallest_x + this->children.back()->smallest_x; // First and last are always shown, rest not + this->ApplyAspectRatio(); } void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override diff --git a/src/smallmap_gui.cpp b/src/smallmap_gui.cpp index 7727478fd1..51cd1e8813 100644 --- a/src/smallmap_gui.cpp +++ b/src/smallmap_gui.cpp @@ -1869,6 +1869,7 @@ public: this->fill_y = (display->fill_y == 0 && bar->fill_y == 0) ? 0 : std::min(display->fill_y, bar->fill_y); this->resize_x = std::max(display->resize_x, bar->resize_x); this->resize_y = std::min(display->resize_y, bar->resize_y); + this->ApplyAspectRatio(); } void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) override diff --git a/src/widget.cpp b/src/widget.cpp index 74e970eb66..2097bba3d3 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -925,6 +925,20 @@ NWidgetBase *NWidgetBase::GetWidgetOfType(WidgetType tp) return (this->type == tp) ? this : nullptr; } +void NWidgetBase::ApplyAspectRatio() +{ + if (this->aspect_ratio == 0) return; + if (this->smallest_x == 0 || this->smallest_y == 0) return; + + uint x = this->smallest_x; + uint y = this->smallest_y; + if ((this->aspect_flags & AspectFlags::ResizeX) == AspectFlags::ResizeX) x = std::max(this->smallest_x, static_cast(this->smallest_y * std::abs(this->aspect_ratio))); + if ((this->aspect_flags & AspectFlags::ResizeY) == AspectFlags::ResizeY) y = std::max(this->smallest_y, static_cast(this->smallest_x / std::abs(this->aspect_ratio))); + + this->smallest_x = x; + this->smallest_y = y; +} + void NWidgetBase::AdjustPaddingForZoom() { this->padding = ScaleGUITrad(this->uz_padding); @@ -942,6 +956,28 @@ NWidgetResizeBase::NWidgetResizeBase(WidgetType tp, uint fill_x, uint fill_y) : this->fill_y = fill_y; } +/** + * Set desired aspect ratio of this widget. + * @param ratio Desired aspect ratio, or 0 for none. + * @param flags Dimensions which should be resized. + */ +void NWidgetResizeBase::SetAspect(float ratio, AspectFlags flags) +{ + this->aspect_ratio = ratio; + this->aspect_flags = flags; +} + +/** + * Set desired aspect ratio of this widget, in terms of horizontal and vertical dimensions. + * @param x_ratio Desired horizontal component of aspect ratio. + * @param y_ratio Desired vertical component of aspect ratio. + * @param flags Dimensions which should be resized. + */ +void NWidgetResizeBase::SetAspect(int x_ratio, int y_ratio, AspectFlags flags) +{ + this->SetAspect(static_cast(x_ratio) / static_cast(y_ratio), flags); +} + void NWidgetResizeBase::AdjustPaddingForZoom() { if (!this->absolute) { @@ -1224,6 +1260,7 @@ void NWidgetStacked::SetupSmallestSize(Window *w) this->fill_y = fill.height; this->resize_x = resize.width; this->resize_y = resize.height; + this->ApplyAspectRatio(); return; } @@ -1243,6 +1280,7 @@ void NWidgetStacked::SetupSmallestSize(Window *w) this->fill_y = std::lcm(this->fill_y, child_wid->fill_y); this->resize_x = std::lcm(this->resize_x, child_wid->resize_x); this->resize_y = std::lcm(this->resize_y, child_wid->resize_y); + this->ApplyAspectRatio(); } } @@ -1405,6 +1443,11 @@ void NWidgetHorizontal::SetupSmallestSize(Window *w) this->smallest_y = cur_height; // Smallest height got changed, try again. } /* 2. For containers that must maintain equal width, extend child minimal size. */ + for (const auto &child_wid : this->children) { + child_wid->smallest_y = this->smallest_y - child_wid->padding.Vertical(); + child_wid->ApplyAspectRatio(); + longest = std::max(longest, child_wid->smallest_x); + } if (this->flags & NC_EQUALSIZE) { for (const auto &child_wid : this->children) { if (child_wid->fill_x == 1) child_wid->smallest_x = longest; @@ -1594,6 +1637,11 @@ void NWidgetVertical::SetupSmallestSize(Window *w) this->smallest_x = cur_width; // Smallest width got changed, try again. } /* 2. For containers that must maintain equal width, extend children minimal size. */ + for (const auto &child_wid : this->children) { + child_wid->smallest_x = this->smallest_x - child_wid->padding.Horizontal(); + child_wid->ApplyAspectRatio(); + highest = std::max(highest, child_wid->smallest_y); + } if (this->flags & NC_EQUALSIZE) { for (const auto &child_wid : this->children) { if (child_wid->fill_y == 1) child_wid->smallest_y = highest; @@ -1730,6 +1778,7 @@ void NWidgetSpacer::SetupSmallestSize(Window *) { this->smallest_x = this->min_x; this->smallest_y = this->min_y; + this->ApplyAspectRatio(); } void NWidgetSpacer::FillWidgetLookup(WidgetLookup &) @@ -1841,6 +1890,7 @@ void NWidgetMatrix::SetupSmallestSize(Window *w) this->fill_y = fill.height; this->resize_x = resize.width; this->resize_y = resize.height; + this->ApplyAspectRatio(); } void NWidgetMatrix::AssignSizePosition(SizingType, int x, int y, uint given_width, uint given_height, bool) @@ -2089,6 +2139,7 @@ void NWidgetBackground::SetupSmallestSize(Window *w) this->smallest_x += this->child->padding.Horizontal(); this->smallest_y += this->child->padding.Vertical(); } + this->ApplyAspectRatio(); } else { Dimension d = {this->min_x, this->min_y}; Dimension fill = {this->fill_x, this->fill_y}; @@ -2117,6 +2168,7 @@ void NWidgetBackground::SetupSmallestSize(Window *w) this->fill_y = fill.height; this->resize_x = resize.width; this->resize_y = resize.height; + this->ApplyAspectRatio(); } } @@ -2203,6 +2255,7 @@ void NWidgetViewport::SetupSmallestSize(Window *) { this->smallest_x = this->min_x; this->smallest_y = this->min_y; + this->ApplyAspectRatio(); } void NWidgetViewport::Draw(const Window *w) @@ -2738,6 +2791,7 @@ void NWidgetLeaf::SetupSmallestSize(Window *w) this->fill_y = fill.height; this->resize_x = resize.width; this->resize_y = resize.height; + this->ApplyAspectRatio(); } void NWidgetLeaf::Draw(const Window *w) @@ -3045,6 +3099,13 @@ static const NWidgetPart *MakeNWidget(const NWidgetPart *nwid_begin, const NWidg break; } + case WPT_ASPECT: { + if (dest == nullptr) [[unlikely]] throw std::runtime_error("WPT_ASPECT requires NWidgetBase"); + dest->aspect_ratio = nwid_begin->u.aspect.ratio; + dest->aspect_flags = nwid_begin->u.aspect.flags; + break; + } + case WPT_ENDCONTAINER: return nwid_begin; diff --git a/src/widget_type.h b/src/widget_type.h index f64ba8f7f5..9a27132873 100644 --- a/src/widget_type.h +++ b/src/widget_type.h @@ -100,6 +100,7 @@ enum WidgetType { WPT_ENDCONTAINER, ///< Widget part to denote end of a container. WPT_FUNCTION, ///< Widget part for calling a user function. WPT_SCROLLBAR, ///< Widget part for attaching a scrollbar. + WPT_ASPECT, ///< Widget part for sepcifying aspect ratio. /* Pushable window widget types. */ WWT_MASK = 0x7F, @@ -119,6 +120,13 @@ enum SizingType { ST_RESIZE, ///< Resize the nested widget tree. }; +enum class AspectFlags : uint8_t { + ResizeX = 1U << 0, + ResizeY = 1U << 1, + ResizeXY = ResizeX | ResizeY, +}; +DECLARE_ENUM_AS_BIT_SET(AspectFlags) + /* Forward declarations. */ class NWidgetCore; class Scrollbar; @@ -136,6 +144,7 @@ class NWidgetBase : public ZeroedMemoryAllocator { public: NWidgetBase(WidgetType tp); + void ApplyAspectRatio(); virtual void AdjustPaddingForZoom(); virtual void SetupSmallestSize(Window *w) = 0; virtual void AssignSizePosition(SizingType sizing, int x, int y, uint given_width, uint given_height, bool rtl) = 0; @@ -232,6 +241,8 @@ public: /* Current widget size (that is, after resizing). */ uint current_x; ///< Current horizontal size (after resizing). uint current_y; ///< Current vertical size (after resizing). + float aspect_ratio = 0; ///< Desired aspect ratio of widget. + AspectFlags aspect_flags = AspectFlags::ResizeX; ///< Which dimensions can be resized. int pos_x; ///< Horizontal position of top-left corner of the widget in the window. int pos_y; ///< Vertical position of top-left corner of the widget in the window. @@ -298,6 +309,8 @@ public: void SetMinimalTextLines(uint8_t min_lines, uint8_t spacing, FontSize size); void SetFill(uint fill_x, uint fill_y); void SetResize(uint resize_x, uint resize_y); + void SetAspect(float ratio, AspectFlags flags = AspectFlags::ResizeX); + void SetAspect(int x_ratio, int y_ratio, AspectFlags flags = AspectFlags::ResizeX); bool UpdateMultilineWidgetSize(const std::string &str, int max_lines); bool UpdateSize(uint min_x, uint min_y); @@ -1045,6 +1058,11 @@ struct NWidgetPartAlignment { StringAlignment align; ///< Alignment of text/image. }; +struct NWidgetPartAspect { + float ratio; + AspectFlags flags; +}; + /** * Pointer to function returning a nested widget. * @return Nested widget (tree). @@ -1068,6 +1086,7 @@ struct NWidgetPart { NWidgetPartAlignment align; ///< Part with internal alignment. NWidgetFunctionType *func_ptr; ///< Part with a function call. NWidContainerFlags cont_flags; ///< Part with container flags. + NWidgetPartAspect aspect; ///< Part to set aspect ratio. /* Constructors for each NWidgetPartUnion data type. */ constexpr NWidgetPartUnion() : xy() {} @@ -1081,6 +1100,7 @@ struct NWidgetPart { constexpr NWidgetPartUnion(NWidgetPartAlignment align) : align(align) {} constexpr NWidgetPartUnion(NWidgetFunctionType *func_ptr) : func_ptr(func_ptr) {} constexpr NWidgetPartUnion(NWidContainerFlags cont_flags) : cont_flags(cont_flags) {} + constexpr NWidgetPartUnion(NWidgetPartAspect aspect) : aspect(aspect) {} } u; /* Constructors for each NWidgetPart data type. */ @@ -1095,6 +1115,7 @@ struct NWidgetPart { constexpr NWidgetPart(WidgetType type, NWidgetPartAlignment align) : type(type), u(align) {} constexpr NWidgetPart(WidgetType type, NWidgetFunctionType *func_ptr) : type(type), u(func_ptr) {} constexpr NWidgetPart(WidgetType type, NWidContainerFlags cont_flags) : type(type), u(cont_flags) {} + constexpr NWidgetPart(WidgetType type, NWidgetPartAspect aspect) : type(type), u(aspect) {} }; /** @@ -1266,6 +1287,17 @@ constexpr NWidgetPart SetScrollbar(WidgetID index) return NWidgetPart{WPT_SCROLLBAR, NWidgetPartWidget{INVALID_COLOUR, index}}; } +/** + * Widget part function for setting the aspect ratio. + * @param ratio Desired aspect ratio, or 0 for none. + * @param flags Dimensions which should be resized. + * @ingroup NestedWidgetParts + */ +constexpr NWidgetPart SetAspect(float ratio, AspectFlags flags = AspectFlags::ResizeX) +{ + return NWidgetPart{WPT_ASPECT, NWidgetPartAspect{ratio, flags}}; +} + /** * Widget part function for starting a new 'real' widget. * @param tp Type of the new nested widget.