/* * Copyright (c) 2018-2021, Andreas Kling * Copyright (c) 2021-2022, Sam Atkins * Copyright (c) 2022-2023, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include namespace Gfx { template class Rect { public: Rect() = default; Rect(T x, T y, T width, T height) : m_location(x, y) , m_size(width, height) { } template Rect(U x, U y, U width, U height) : m_location(x, y) , m_size(width, height) { } Rect(Point const& location, Size const& size) : m_location(location) , m_size(size) { } template Rect(Point const& location, Size const& size) : m_location(location) , m_size(size) { } template explicit Rect(Rect const& other) : m_location(other.location()) , m_size(other.size()) { } [[nodiscard]] ALWAYS_INLINE T x() const { return location().x(); } [[nodiscard]] ALWAYS_INLINE T y() const { return location().y(); } [[nodiscard]] ALWAYS_INLINE T width() const { return m_size.width(); } [[nodiscard]] ALWAYS_INLINE T height() const { return m_size.height(); } ALWAYS_INLINE void set_x(T x) { m_location.set_x(x); } ALWAYS_INLINE void set_y(T y) { m_location.set_y(y); } ALWAYS_INLINE void set_width(T width) { m_size.set_width(width); } ALWAYS_INLINE void set_height(T height) { m_size.set_height(height); } [[nodiscard]] ALWAYS_INLINE Point const& location() const { return m_location; } [[nodiscard]] ALWAYS_INLINE Size const& size() const { return m_size; } [[nodiscard]] ALWAYS_INLINE bool is_empty() const { return width() <= 0 || height() <= 0; } ALWAYS_INLINE void translate_by(T dx, T dy) { m_location.translate_by(dx, dy); } ALWAYS_INLINE void translate_by(T dboth) { m_location.translate_by(dboth); } ALWAYS_INLINE void translate_by(Point const& delta) { m_location.translate_by(delta); } ALWAYS_INLINE void scale_by(T dx, T dy) { m_location.scale_by(dx, dy); m_size.scale_by(dx, dy); } ALWAYS_INLINE void scale_by(T dboth) { scale_by(dboth, dboth); } ALWAYS_INLINE void scale_by(Point const& delta) { scale_by(delta.x(), delta.y()); } void transform_by(AffineTransform const& transform) { *this = transform.map(*this); } [[nodiscard]] Point center() const { return { x() + width() / 2, y() + height() / 2 }; } ALWAYS_INLINE void set_location(Point const& location) { m_location = location; } ALWAYS_INLINE void set_size(Size const& size) { m_size = size; } void set_size(T width, T height) { m_size.set_width(width); m_size.set_height(height); } void inflate(T w, T h) { set_x(x() - w / 2); set_width(width() + w); set_y(y() - h / 2); set_height(height() + h); } void inflate(T top, T right, T bottom, T left) { set_x(x() - left); set_width(width() + left + right); set_y(y() - top); set_height(height() + top + bottom); } void inflate(Size const& size) { set_x(x() - size.width() / 2); set_width(width() + size.width()); set_y(y() - size.height() / 2); set_height(height() + size.height()); } void shrink(T w, T h) { set_x(x() + w / 2); set_width(width() - w); set_y(y() + h / 2); set_height(height() - h); } void shrink(T top, T right, T bottom, T left) { set_x(x() + left); set_width(width() - (left + right)); set_y(y() + top); set_height(height() - (top + bottom)); } void shrink(Size const& size) { set_x(x() + size.width() / 2); set_width(width() - size.width()); set_y(y() + size.height() / 2); set_height(height() - size.height()); } [[nodiscard]] Rect translated(T dx, T dy) const { Rect rect = *this; rect.translate_by(dx, dy); return rect; } [[nodiscard]] Rect translated(T dboth) const { Rect rect = *this; rect.translate_by(dboth); return rect; } [[nodiscard]] Rect translated(Point const& delta) const { Rect rect = *this; rect.translate_by(delta); return rect; } [[nodiscard]] Rect scaled(T dboth) const { Rect rect = *this; rect.scale_by(dboth); return rect; } [[nodiscard]] Rect scaled(T sx, T sy) const { Rect rect = *this; rect.scale_by(sx, sy); return rect; } [[nodiscard]] Rect scaled(Point const& s) const { Rect rect = *this; rect.scale_by(s); return rect; } [[nodiscard]] Rect transformed(AffineTransform const& transform) const { Rect rect = *this; rect.transform_by(transform); return rect; } [[nodiscard]] Rect shrunken(T w, T h) const { Rect rect = *this; rect.shrink(w, h); return rect; } [[nodiscard]] Rect shrunken(T top, T right, T bottom, T left) const { Rect rect = *this; rect.shrink(top, right, bottom, left); return rect; } [[nodiscard]] Rect shrunken(Size const& size) const { Rect rect = *this; rect.shrink(size); return rect; } [[nodiscard]] Rect inflated(T w, T h) const { Rect rect = *this; rect.inflate(w, h); return rect; } [[nodiscard]] Rect inflated(T top, T right, T bottom, T left) const { Rect rect = *this; rect.inflate(top, right, bottom, left); return rect; } [[nodiscard]] Rect inflated(Size const& size) const { Rect rect = *this; rect.inflate(size); return rect; } Rect take_from_right(T w) { if (w > width()) w = width(); Rect rect = *this; set_width(width() - w); rect.set_x(x() + width()); rect.set_width(w); return rect; } Rect take_from_left(T w) { if (w > width()) w = width(); Rect rect = *this; set_x(x() + w); set_width(width() - w); rect.set_width(w); return rect; } Rect take_from_top(T h) { if (h > height()) h = height(); Rect rect = *this; set_y(y() + h); set_height(height() - h); rect.set_height(h); return rect; } Rect take_from_bottom(T h) { if (h > height()) h = height(); Rect rect = *this; set_height(height() - h); rect.set_y(y() + height()); rect.set_height(h); return rect; } [[nodiscard]] bool contains_vertically(T y) const { return y >= top() && y < bottom(); } [[nodiscard]] bool contains_horizontally(T x) const { return x >= left() && x < right(); } [[nodiscard]] bool contains(T x, T y) const { return contains_horizontally(x) && contains_vertically(y); } [[nodiscard]] ALWAYS_INLINE bool contains(Point const& point) const { return contains(point.x(), point.y()); } [[nodiscard]] bool contains(Rect const& other) const { return left() <= other.left() && right() >= other.right() && top() <= other.top() && bottom() >= other.bottom(); } template [[nodiscard]] bool contains(Container const& others) const { bool have_any = false; for (auto const& other : others) { if (!contains(other)) return false; have_any = true; } return have_any; } [[nodiscard]] ALWAYS_INLINE T primary_offset_for_orientation(Orientation orientation) const { return m_location.primary_offset_for_orientation(orientation); } ALWAYS_INLINE void set_primary_offset_for_orientation(Orientation orientation, T value) { m_location.set_primary_offset_for_orientation(orientation, value); } [[nodiscard]] ALWAYS_INLINE T secondary_offset_for_orientation(Orientation orientation) const { return m_location.secondary_offset_for_orientation(orientation); } ALWAYS_INLINE void set_secondary_offset_for_orientation(Orientation orientation, T value) { m_location.set_secondary_offset_for_orientation(orientation, value); } [[nodiscard]] ALWAYS_INLINE T primary_size_for_orientation(Orientation orientation) const { return m_size.primary_size_for_orientation(orientation); } [[nodiscard]] ALWAYS_INLINE T secondary_size_for_orientation(Orientation orientation) const { return m_size.secondary_size_for_orientation(orientation); } ALWAYS_INLINE void set_primary_size_for_orientation(Orientation orientation, T value) { m_size.set_primary_size_for_orientation(orientation, value); } ALWAYS_INLINE void set_secondary_size_for_orientation(Orientation orientation, T value) { m_size.set_secondary_size_for_orientation(orientation, value); } [[nodiscard]] T first_edge_for_orientation(Orientation orientation) const { if (orientation == Orientation::Vertical) return top(); return left(); } [[nodiscard]] T last_edge_for_orientation(Orientation orientation) const { if (orientation == Orientation::Vertical) return bottom(); return right(); } [[nodiscard]] ALWAYS_INLINE T left() const { return x(); } [[nodiscard]] ALWAYS_INLINE T right() const { return x() + width(); } [[nodiscard]] ALWAYS_INLINE T top() const { return y(); } [[nodiscard]] ALWAYS_INLINE T bottom() const { return y() + height(); } ALWAYS_INLINE void set_left(T left) { set_x(left); } ALWAYS_INLINE void set_top(T top) { set_y(top); } ALWAYS_INLINE void set_right(T right) { set_width(right - x()); } ALWAYS_INLINE void set_bottom(T bottom) { set_height(bottom - y()); } void set_right_without_resize(T new_right) { auto delta = new_right - right(); translate_by(delta, 0); } void set_bottom_without_resize(T new_bottom) { auto delta = new_bottom - bottom(); translate_by(0, delta); } [[nodiscard]] bool intersects_vertically(Rect const& other) const { return top() < other.bottom() && other.top() < bottom(); } [[nodiscard]] bool intersects_horizontally(Rect const& other) const { return left() < other.right() && other.left() < right(); } [[nodiscard]] bool intersects(Rect const& other) const { return left() < other.right() && other.left() < right() && top() < other.bottom() && other.top() < bottom(); } [[nodiscard]] bool edge_adjacent_intersects(Rect const& other) const { return max(left(), other.left()) <= min(right(), other.right()) && max(top(), other.top()) <= min(bottom(), other.bottom()); } template [[nodiscard]] bool intersects(Container const& others) const { for (auto const& other : others) { if (intersects(other)) return true; } return false; } template [[nodiscard]] bool operator==(Rect const& other) const { return location() == other.location() && size() == other.size(); } [[nodiscard]] Rect operator*(T factor) const { return { m_location * factor, m_size * factor }; } Rect& operator*=(T factor) { m_location *= factor; m_size *= factor; return *this; } void intersect(Rect const& other) { T l = max(left(), other.left()); T r = min(right(), other.right()); T t = max(top(), other.top()); T b = min(bottom(), other.bottom()); if (l > r || t > b) { m_location = {}; m_size = {}; return; } set_x(l); set_y(t); set_right(r); set_bottom(b); } [[nodiscard]] static Rect centered_on(Point const& center, Size const& size) { return { { center.x() - size.width() / 2, center.y() - size.height() / 2 }, size }; } [[nodiscard]] static Rect from_two_points(Point const& a, Point const& b) { return { min(a.x(), b.x()), min(a.y(), b.y()), AK::abs(a.x() - b.x()), AK::abs(a.y() - b.y()) }; } [[nodiscard]] static Rect intersection(Rect const& a, Rect const& b) { Rect r = a; r.intersect(b); return r; } [[nodiscard]] ALWAYS_INLINE Rect intersected(Rect const& other) const { return intersection(*this, other); } template [[nodiscard]] Gfx::Rect interpolated_to(Gfx::Rect const& to, float factor) const { VERIFY(factor >= 0.f); VERIFY(factor <= 1.f); if (factor == 0.f) return *this; if (factor == 1.f) return to; if (this == &to) return *this; auto interpolated_left = round_to(mix(x(), to.x(), factor)); auto interpolated_top = round_to(mix(y(), to.y(), factor)); auto interpolated_right = round_to(mix(right(), to.right(), factor)); auto interpolated_bottom = round_to(mix(bottom(), to.bottom(), factor)); return { interpolated_left, interpolated_top, interpolated_right - interpolated_left, interpolated_bottom - interpolated_top }; } [[nodiscard]] static Rect centered_at(Point const& point, Size const& size) { return { { point.x() - size.width() / 2, point.y() - size.height() / 2 }, size }; } void unite(Rect const& other) { if (is_empty()) { *this = other; return; } if (other.is_empty()) return; unite_horizontally(other); unite_vertically(other); } void unite_horizontally(Rect const& other) { auto new_left = min(left(), other.left()); auto new_right = max(right(), other.right()); set_left(new_left); set_right(new_right); } void unite_vertically(Rect const& other) { auto new_top = min(top(), other.top()); auto new_bottom = max(bottom(), other.bottom()); set_top(new_top); set_bottom(new_bottom); } [[nodiscard]] Rect united(Rect const& other) const { Rect rect = *this; rect.unite(other); return rect; } [[nodiscard]] Point top_left() const { return { left(), top() }; } [[nodiscard]] Point top_right() const { return { right(), top() }; } [[nodiscard]] Point bottom_left() const { return { left(), bottom() }; } [[nodiscard]] Point bottom_right() const { return { right(), bottom() }; } void align_within(Rect const& other, TextAlignment alignment) { switch (alignment) { case TextAlignment::Center: center_within(other); return; case TextAlignment::TopCenter: center_horizontally_within(other); set_y(other.y()); return; case TextAlignment::TopLeft: set_location(other.location()); return; case TextAlignment::TopRight: set_x(other.right() - width()); set_y(other.y()); return; case TextAlignment::CenterLeft: set_x(other.x()); center_vertically_within(other); return; case TextAlignment::CenterRight: set_x(other.right() - width()); center_vertically_within(other); return; case TextAlignment::BottomCenter: center_horizontally_within(other); set_y(other.bottom() - height()); return; case TextAlignment::BottomLeft: set_x(other.x()); set_y(other.bottom() - height()); return; case TextAlignment::BottomRight: set_x(other.right() - width()); set_y(other.bottom() - height()); return; } } void center_within(Rect const& other) { center_horizontally_within(other); center_vertically_within(other); } [[nodiscard]] Rect centered_within(Rect const& other) const { Rect rect { *this }; rect.center_horizontally_within(other); rect.center_vertically_within(other); return rect; } void center_horizontally_within(Rect const& other) { set_x(other.center().x() - width() / 2); } void center_vertically_within(Rect const& other) { set_y(other.center().y() - height() / 2); } template requires(!IsSame) [[nodiscard]] ALWAYS_INLINE Rect to_type() const { return Rect(*this); } // For extern specialization, like CSSPixels template [[nodiscard]] Rect to_rounded() const = delete; template [[nodiscard]] ALWAYS_INLINE Rect to_rounded() const { // FIXME: We may get away with `rint[lf]?()` here. // This would even give us some more control of these internals, // while the break-tie algorithm does not really matter if constexpr (IsSame) { return { static_cast(roundf(x())), static_cast(roundf(y())), static_cast(roundf(width())), static_cast(roundf(height())), }; } if constexpr (IsSame) { return { static_cast(round(x())), static_cast(round(y())), static_cast(round(width())), static_cast(round(height())), }; } return { static_cast(roundl(x())), static_cast(roundl(y())), static_cast(roundl(width())), static_cast(roundl(height())), }; } template ALWAYS_INLINE Rect to_rounded() const { return { round_to(x()), round_to(y()), round_to(width()), round_to(height()), }; } [[nodiscard]] ByteString to_byte_string() const; private: Point m_location; Size m_size; }; using IntRect = Rect; using FloatRect = Rect; using DoubleRect = Rect; [[nodiscard]] ALWAYS_INLINE IntRect enclosing_int_rect(FloatRect const& float_rect) { int x1 = floorf(float_rect.x()); int y1 = floorf(float_rect.y()); int x2 = ceilf(float_rect.right()); int y2 = ceilf(float_rect.bottom()); return Gfx::IntRect::from_two_points({ x1, y1 }, { x2, y2 }); } } namespace AK { template struct Formatter> : Formatter { ErrorOr format(FormatBuilder& builder, Gfx::Rect const& value) { return Formatter::format(builder, "[{},{} {}x{}]"sv, value.x(), value.y(), value.width(), value.height()); } }; } namespace IPC { template<> ErrorOr encode(Encoder&, Gfx::IntRect const&); template<> ErrorOr decode(Decoder&); }