ladybird/Userland/Libraries/LibGUI/UIDimensions.h
tetektoza d2995f7517 LibGUI: Add missing constructor to UISize class for fixed_size property
This patch adds a missing constructor to UISize class for fixed_size
property, so the property can take an array if user specified it
in .gml file.
2023-10-24 21:47:18 +02:00

354 lines
12 KiB
C++

/*
* Copyright (c) 2022, Frhun <serenitystuff@frhun.de>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/JsonValue.h>
#include <AK/Optional.h>
#include <AK/String.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))
{
}
[[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();
}
/// The returned source code, if any, can be used to construct this UIDimension in C++.
ErrorOr<String> as_cpp_source() const
{
String value_source = {};
if (is_int())
value_source = TRY(String::number(m_value));
else if (is_shrink())
value_source = "GUI::SpecialDimension::Shrink"_string;
else if (is_grow())
value_source = "GUI::SpecialDimension::Grow"_string;
else if (is_opportunistic_grow())
value_source = "GUI::SpecialDimension::OpportunisticGrow"_string;
else if (is_fit())
value_source = "GUI::SpecialDimension::Fit"_string;
return String::formatted("GUI::UIDimension {{ {} }}", value_source);
}
[[nodiscard]] static Optional<UIDimension> construct_from_json_value(AK::JsonValue const value)
{
if (value.is_string()) {
DeprecatedString 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 if (value.is_integer<i32>()) {
auto value_int = value.as_integer<i32>();
if (value_int < 0)
return {};
return UIDimension(value_int);
}
return {};
}
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(Array<i64, 2> size)
: UISize(size[0], size[1])
{
}
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);
}
explicit 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);
}
}
#define REGISTER_UI_DIMENSION_PROPERTY(property_name, getter, setter) \
register_property( \
property_name, \
[this] { \
return this->getter().as_json_value(); \
}, \
[this](auto& value) { \
auto result = GUI::UIDimension::construct_from_json_value(value); \
if (result.has_value()) \
this->setter(result.value()); \
return result.has_value(); \
});
#define REGISTER_READONLY_UI_DIMENSION_PROPERTY(property_name, getter) \
register_property( \
property_name, \
[this] { \
return this->getter().as_json_value(); \
});
#define REGISTER_UI_SIZE_PROPERTY(property_name, getter, setter) \
register_property( \
property_name, \
[this] { \
auto size = this->getter(); \
JsonObject size_object; \
size_object.set("width"sv, size.width().as_json_value()); \
size_object.set("height"sv, size.height().as_json_value()); \
return size_object; \
}, \
[this](auto& value) { \
if (!value.is_object()) \
return false; \
auto result_width = GUI::UIDimension::construct_from_json_value( \
value.as_object().get("width"sv).value_or({})); \
auto result_height = GUI::UIDimension::construct_from_json_value( \
value.as_object().get("height"sv).value_or({})); \
if (result_width.has_value() && result_height.has_value()) { \
GUI::UISize size(result_width.value(), result_height.value()); \
setter(size); \
return true; \
} \
return false; \
});
#define REGISTER_READONLY_UI_SIZE_PROPERTY(property_name, getter) \
register_property( \
property_name, \
[this] { \
auto size = this->getter(); \
JsonObject size_object; \
size_object.set("width", size.width().as_json_value()); \
size_object.set("height", size.height().as_json_value()); \
return size_object; \
});