diff --git a/Userland/Libraries/LibGUI/AbstractScrollableWidget.cpp b/Userland/Libraries/LibGUI/AbstractScrollableWidget.cpp index 30e55ca91b..5be629187e 100644 --- a/Userland/Libraries/LibGUI/AbstractScrollableWidget.cpp +++ b/Userland/Libraries/LibGUI/AbstractScrollableWidget.cpp @@ -75,8 +75,8 @@ void AbstractScrollableWidget::mousewheel_event(MouseEvent& event) void AbstractScrollableWidget::custom_layout() { auto inner_rect = frame_inner_rect_for_size(size()); - int height_wanted_by_horizontal_scrollbar = m_horizontal_scrollbar->is_visible() ? m_horizontal_scrollbar->min_height() : 0; - int width_wanted_by_vertical_scrollbar = m_vertical_scrollbar->is_visible() ? m_vertical_scrollbar->min_width() : 0; + int height_wanted_by_horizontal_scrollbar = m_horizontal_scrollbar->is_visible() ? int(m_horizontal_scrollbar->min_height()) : 0; + int width_wanted_by_vertical_scrollbar = m_vertical_scrollbar->is_visible() ? int(m_vertical_scrollbar->min_width()) : 0; m_vertical_scrollbar->set_relative_rect( inner_rect.right() + 1 - m_vertical_scrollbar->min_width(), diff --git a/Userland/Libraries/LibGUI/UIDimensions.h b/Userland/Libraries/LibGUI/UIDimensions.h new file mode 100644 index 0000000000..b2061a5b45 --- /dev/null +++ b/Userland/Libraries/LibGUI/UIDimensions.h @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2022, Frhun + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace GUI { + +// The constants used for special values +// Their order here, also defines their order among each other for min, max; operations, excluding Regular +enum class SpecialDimension : int { + Regular = 0, // only really useful for is_one_of + Grow = -1, + OpportunisticGrow = -2, + Fit = -3, + Shrink = -4, +}; + +class UIDimension { + friend constexpr auto AK::max(GUI::UIDimension const&, GUI::UIDimension const&) -> GUI::UIDimension; + friend constexpr auto AK::min(GUI::UIDimension const&, GUI::UIDimension const&) -> GUI::UIDimension; + +public: + UIDimension() = delete; + + UIDimension(int value) + : m_value(value) + { + VERIFY(value >= 0); + } + + UIDimension(SpecialDimension special) + : m_value(to_underlying(special)) + { + } + + // This is a temporary hack to get this compiling + operator int() const + { + return m_value; + } + + [[nodiscard]] inline bool is_special_value() const + { + return m_value < 0; + } + + [[nodiscard]] inline bool is_int() const + { + return m_value >= 0; + } + + [[nodiscard]] inline bool is_shrink() const + { + return m_value == to_underlying(SpecialDimension::Shrink); + } + + [[nodiscard]] inline bool is_grow() const + { + return m_value == to_underlying(SpecialDimension::Grow); + } + + [[nodiscard]] inline bool is_opportunistic_grow() const + { + return m_value == to_underlying(SpecialDimension::OpportunisticGrow); + } + + [[nodiscard]] inline bool is_fit() const + { + return m_value == to_underlying(SpecialDimension::Fit); + } + + [[nodiscard]] inline bool is_one_of(std::initializer_list valid_values) const + { + for (SpecialDimension v : valid_values) { + if (m_value == to_underlying(v) || (v == SpecialDimension::Regular && is_int())) + return true; + } + return false; + } + + [[nodiscard]] ALWAYS_INLINE constexpr bool is(SpecialDimension special_value) const + { + return m_value == to_underlying(special_value) || (special_value == SpecialDimension::Regular && is_int()); + } + + template + [[nodiscard]] bool is_one_of(Ts... valid_values) const + { + return (... || (is(forward(valid_values)))); + } + + [[nodiscard]] inline bool operator==(UIDimension other) const + { + return m_value == other.m_value; + } + + [[nodiscard]] inline UIDimension must_sum_with(UIDimension other) const + { + VERIFY(is_int() && other.is_int()); + return UIDimension { m_value + other.m_value }; + } + + inline void must_add(int to_add) + { + VERIFY(is_int()); + VERIFY(m_value >= -to_add); + m_value += to_add; + } + + inline void add_if_int(int to_add) + { + if (is_int()) { + m_value += to_add; + } + } + + [[nodiscard]] inline ErrorOr shrink_value() const + { + if (m_value >= 0) + return m_value; + if (m_value == to_underlying(SpecialDimension::Shrink)) + return 0; + return Error::from_string_literal("value is neither shrink nor an integer ≥0"); + } + + [[nodiscard]] inline int as_int() const + { + VERIFY(is_int()); + return m_value; + } + + [[nodiscard]] AK::JsonValue as_json_value() const + { + if (is_int()) + return m_value; + if (is_shrink()) + return "shrink"; + if (is_grow()) + return "grow"; + if (is_opportunistic_grow()) + return "opportunistic_grow"; + if (is_fit()) + return "fit"; + + VERIFY_NOT_REACHED(); + } + + operator AK::JsonValue() const + { + return this->as_json_value(); + } + + [[nodiscard]] static Optional construct_from_json_value(AK::JsonValue const value) + { + if (value.is_string()) { + String value_literal = value.as_string(); + if (value_literal == "shrink") + return UIDimension { SpecialDimension::Shrink }; + else if (value_literal == "grow") + return UIDimension { SpecialDimension::Grow }; + else if (value_literal == "opportunistic_grow") + return UIDimension { SpecialDimension::OpportunisticGrow }; + else if (value_literal == "fit") + return UIDimension { SpecialDimension::Fit }; + else + return {}; + } else { + int value_int = value.to_i32(); + if (value_int < 0) + return {}; + return UIDimension(value_int); + } + } + + // FIXME: Remove these following methods when the move to the new layout system is completed + [[nodiscard]] inline bool operator==(int other) const + { + return m_value == other; + } + +private: + int m_value; +}; + +class UISize : public Gfx::Size { + +public: + UISize() = delete; + + UISize(int in_width, int in_height) + : Gfx::Size(in_width, in_height) + { + } + + UISize(Gfx::IntSize size) + : UISize(size.width(), size.height()) + { + } + + UISize(SpecialDimension special) + : Gfx::Size(UIDimension { special }, UIDimension { special }) + { + } + + UISize(UIDimension width, UIDimension height) + : Gfx::Size(width, height) + { + } + + inline UISize replace_component_if_matching_with(UIDimension to_match, UISize replacement) + { + if (width() == to_match) + set_width(replacement.width()); + if (height() == to_match) + set_height(replacement.height()); + return *this; + } + + [[nodiscard]] inline bool has_only_int_values() const + { + return width().is_int() && height().is_int(); + } + + [[nodiscard]] inline bool either_is(UIDimension to_match) const + { + return (width() == to_match || height() == to_match); + } + + operator Gfx::IntSize() const + { + return Gfx::IntSize(width().as_int(), height().as_int()); + } +}; + +} + +namespace AK { + +template<> +inline auto max(GUI::UIDimension const& a, GUI::UIDimension const& b) -> GUI::UIDimension +{ + if ((a.is_int() && b.is_int()) || (a.is_special_value() && b.is_special_value())) + return a.m_value > b.m_value ? a : b; + if (a.is_grow() || b.is_grow()) + return GUI::SpecialDimension::Grow; + if (a.is_opportunistic_grow() || b.is_opportunistic_grow()) + return GUI::SpecialDimension::OpportunisticGrow; + if (a.is_fit() || b.is_fit()) + return GUI::SpecialDimension::Fit; + if (a.is_shrink()) + return b; + if (b.is_shrink()) + return a; + VERIFY_NOT_REACHED(); +} + +template<> +inline auto min(GUI::UIDimension const& a, GUI::UIDimension const& b) -> GUI::UIDimension +{ + if ((a.is_int() && b.is_int()) || (a.is_special_value() && b.is_special_value())) + return a.m_value < b.m_value ? a : b; + if (a.is_shrink() || b.is_shrink()) + return GUI::SpecialDimension::Shrink; + if (a.is_int()) + return a; + if (b.is_int()) + return b; + if (a.is_fit() || b.is_fit()) + return GUI::SpecialDimension::Fit; + if (a.is_opportunistic_grow() || b.is_opportunistic_grow()) + return GUI::SpecialDimension::OpportunisticGrow; + VERIFY_NOT_REACHED(); +} + +template<> +inline auto clamp(GUI::UIDimension const& input, GUI::UIDimension const& lower_bound, GUI::UIDimension const& upper_bound) -> GUI::UIDimension +{ + return min(max(input, lower_bound), upper_bound); +} + +}