LibGUI: Add UIDimension and UISize types

These types are used to represent size information for layouts.
They can hold integer (int) values ≥0 or special values like shrink
and fit.
This commit is contained in:
FrHun 2022-06-12 20:00:15 +02:00 committed by Sam Atkins
parent e29314f837
commit 024305e742
Notes: sideshowbarker 2024-07-17 09:55:37 +09:00
2 changed files with 292 additions and 2 deletions

View File

@ -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(),

View File

@ -0,0 +1,290 @@
/*
* Copyright (c) 2022, Frhun <serenitystuff@frhun.de>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/JsonValue.h>
#include <AK/Optional.h>
#include <LibGfx/Rect.h>
#include <LibGfx/Size.h>
#include <initializer_list>
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>(GUI::UIDimension const&, GUI::UIDimension const&) -> GUI::UIDimension;
friend constexpr auto AK::min<GUI::UIDimension>(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<SpecialDimension> 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<typename... Ts>
[[nodiscard]] bool is_one_of(Ts... valid_values) const
{
return (... || (is(forward<Ts>(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<int> 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<UIDimension> 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<UIDimension> {
public:
UISize() = delete;
UISize(int in_width, int in_height)
: Gfx::Size<UIDimension>(in_width, in_height)
{
}
UISize(Gfx::IntSize size)
: UISize(size.width(), size.height())
{
}
UISize(SpecialDimension special)
: Gfx::Size<UIDimension>(UIDimension { special }, UIDimension { special })
{
}
UISize(UIDimension width, UIDimension height)
: Gfx::Size<UIDimension>(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>(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>(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>(GUI::UIDimension const& input, GUI::UIDimension const& lower_bound, GUI::UIDimension const& upper_bound) -> GUI::UIDimension
{
return min(max(input, lower_bound), upper_bound);
}
}