1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 23:47:45 +00:00

LibGfx+Everywhere: Change Gfx::Rect to be endpoint exclusive

Previously, calling `.right()` on a `Gfx::Rect` would return the last
column's coordinate still inside the rectangle, or `left + width - 1`.
This is called 'endpoint inclusive' and does not make a lot of sense for
`Gfx::Rect<float>` where a rectangle of width 5 at position (0, 0) would
return 4 as its right side. This same problem exists for `.bottom()`.

This changes `Gfx::Rect` to be endpoint exclusive, which gives us the
nice property that `width = right - left` and `height = bottom - top`.
It enables us to treat `Gfx::Rect<int>` and `Gfx::Rect<float>` exactly
the same.

All users of `Gfx::Rect` have been updated accordingly.
This commit is contained in:
Jelle Raaijmakers 2023-05-22 00:41:18 +02:00 committed by Andreas Kling
parent b7f4363791
commit f391ccfe53
88 changed files with 524 additions and 518 deletions

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl>
* Copyright (c) 2022-2023, Jelle Raaijmakers <jelle@gmta.nl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -9,6 +9,7 @@
#pragma once
#include <AK/Format.h>
#include <AK/Vector.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Line.h>
#include <LibGfx/Orientation.h>
@ -19,12 +20,6 @@
namespace Gfx {
template<typename T>
T abst(T value)
{
return value < 0 ? -value : value;
}
template<typename T>
class Rect {
public:
@ -291,17 +286,17 @@ public:
[[nodiscard]] bool contains_vertically(T y) const
{
return y >= top() && y <= bottom();
return y >= top() && y < bottom();
}
[[nodiscard]] bool contains_horizontally(T x) const
{
return x >= left() && x <= right();
return x >= left() && x < right();
}
[[nodiscard]] bool contains(T x, T y) const
{
return x >= m_location.x() && x <= right() && y >= m_location.y() && y <= bottom();
return contains_horizontally(x) && contains_vertically(y);
}
[[nodiscard]] ALWAYS_INLINE bool contains(Point<T> const& point) const
@ -354,29 +349,14 @@ public:
}
[[nodiscard]] ALWAYS_INLINE T left() const { return x(); }
[[nodiscard]] ALWAYS_INLINE T right() const { return x() + width() - 1; }
[[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() - 1; }
[[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() + 1);
}
ALWAYS_INLINE void set_bottom(T bottom)
{
set_height(bottom - y() + 1);
}
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)
{
@ -392,20 +372,20 @@ public:
[[nodiscard]] bool intersects_vertically(Rect<T> const& other) const
{
return top() <= other.bottom() && other.top() <= bottom();
return top() < other.bottom() && other.top() < bottom();
}
[[nodiscard]] bool intersects_horizontally(Rect<T> const& other) const
{
return left() <= other.right() && other.left() <= right();
return left() < other.right() && other.left() < right();
}
[[nodiscard]] bool intersects(Rect<T> const& other) const
{
return left() <= other.right()
&& other.left() <= right()
&& top() <= other.bottom()
&& other.top() <= bottom();
return left() < other.right()
&& other.left() < right()
&& top() < other.bottom()
&& other.top() < bottom();
}
template<typename Container>
@ -445,25 +425,25 @@ public:
x(),
y(),
width(),
hammer.y() - y()
hammer.y() - y(),
};
Rect<T> bottom_shard {
x(),
hammer.y() + hammer.height(),
hammer.bottom(),
width(),
(y() + height()) - (hammer.y() + hammer.height())
bottom() - hammer.bottom(),
};
Rect<T> left_shard {
x(),
max(hammer.y(), y()),
hammer.x() - x(),
min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y())
min(hammer.bottom(), bottom()) - max(hammer.y(), y()),
};
Rect<T> right_shard {
hammer.x() + hammer.width(),
hammer.right(),
max(hammer.y(), y()),
right() - hammer.right(),
min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y())
min(hammer.bottom(), bottom()) - max(hammer.y(), y()),
};
if (!top_shard.is_empty())
pieces.unchecked_append(top_shard);
@ -505,10 +485,10 @@ public:
return;
}
m_location.set_x(l);
m_location.set_y(t);
m_size.set_width((r - l) + 1);
m_size.set_height((b - t) + 1);
set_x(l);
set_y(t);
set_right(r);
set_bottom(b);
}
[[nodiscard]] static Rect<T> centered_on(Point<T> const& center, Size<T> const& size)
@ -518,7 +498,7 @@ public:
[[nodiscard]] static Rect<T> from_two_points(Point<T> const& a, Point<T> const& b)
{
return { min(a.x(), b.x()), min(a.y(), b.y()), abst(a.x() - b.x()), abst(a.y() - b.y()) };
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<T> intersection(Rect<T> const& a, Rect<T> const& b)
@ -541,18 +521,18 @@ public:
if (auto point = line.intersected({ top_left(), top_right() }); point.has_value())
points.append({ point.value().x(), y() });
if (auto point = line.intersected({ bottom_left(), bottom_right() }); point.has_value()) {
points.append({ point.value().x(), bottom() });
points.append({ point.value().x(), bottom() - 1 });
if (points.size() == 2)
return points;
}
if (height() > 2) {
if (auto point = line.intersected({ { x(), y() + 1 }, { x(), bottom() - 1 } }); point.has_value()) {
if (auto point = line.intersected({ { x(), y() + 1 }, { x(), bottom() - 2 } }); point.has_value()) {
points.append({ x(), point.value().y() });
if (points.size() == 2)
return points;
}
if (auto point = line.intersected({ { right(), y() + 1 }, { right(), bottom() - 1 } }); point.has_value())
points.append({ right(), point.value().y() });
if (auto point = line.intersected({ { right() - 1, y() + 1 }, { right() - 1, bottom() - 2 } }); point.has_value())
points.append({ right() - 1, point.value().y() });
}
return points;
}
@ -560,11 +540,11 @@ public:
template<typename U = T>
[[nodiscard]] Gfx::Rect<U> interpolated_to(Gfx::Rect<T> const& to, float factor) const
{
VERIFY(factor >= 0.0f);
VERIFY(factor <= 1.0f);
if (factor == 0.0f)
VERIFY(factor >= 0.f);
VERIFY(factor <= 1.f);
if (factor == 0.f)
return *this;
if (factor == 1.0f)
if (factor == 1.f)
return to;
if (this == &to)
return *this;
@ -572,7 +552,7 @@ public:
auto interpolated_top = round_to<U>(mix<float>(y(), to.y(), factor));
auto interpolated_right = round_to<U>(mix<float>(right(), to.right(), factor));
auto interpolated_bottom = round_to<U>(mix<float>(bottom(), to.bottom(), factor));
return { interpolated_left, interpolated_top, interpolated_right - interpolated_left + 1, interpolated_bottom - interpolated_top + 1 };
return { interpolated_left, interpolated_top, interpolated_right - interpolated_left, interpolated_bottom - interpolated_top };
}
[[nodiscard]] float center_point_distance_to(Rect<T> const& other) const
@ -596,7 +576,7 @@ public:
{
auto points = closest_outside_center_points(other);
if (points.is_empty())
return 0.0;
return 0.f;
return Line { points[0], points[0] }.length();
}
@ -679,8 +659,8 @@ public:
check_distance({ top_left(), top_right() });
check_distance({ bottom_left(), bottom_right() });
if (height() > 2) {
check_distance({ { x(), y() + 1 }, { x(), bottom() - 1 } });
check_distance({ { right(), y() + 1 }, { right(), bottom() - 1 } });
check_distance({ { x(), y() + 1 }, { x(), bottom() - 2 } });
check_distance({ { right() - 1, y() + 1 }, { right() - 1, bottom() - 2 } });
}
VERIFY(closest_point.has_value());
VERIFY(side(closest_point.value()) != Side::None);
@ -699,23 +679,23 @@ public:
if (part.x() < other_rect.x()) {
if (part.y() < other_rect.y())
m_top_left = true;
if ((part.y() >= other_rect.y() && part.y() < other_rect.bottom()) || (part.y() <= other_rect.bottom() && part.bottom() > other_rect.y()))
if ((part.y() >= other_rect.y() && part.y() < other_rect.bottom() - 1) || (part.y() < other_rect.bottom() && part.bottom() - 1 > other_rect.y()))
m_left = true;
if (part.y() >= other_rect.bottom() || part.bottom() > other_rect.y())
if (part.y() >= other_rect.bottom() - 1 || part.bottom() - 1 > other_rect.y())
m_bottom_left = true;
}
if (part.x() >= other_rect.x() || part.right() > other_rect.x()) {
if (part.x() >= other_rect.x() || part.right() - 1 > other_rect.x()) {
if (part.y() < other_rect.y())
m_top = true;
if (part.y() >= other_rect.bottom() || part.bottom() > other_rect.bottom())
if (part.y() >= other_rect.bottom() - 1 || part.bottom() > other_rect.bottom())
m_bottom = true;
}
if (part.x() >= other_rect.right() || part.right() > other_rect.right()) {
if (part.x() >= other_rect.right() - 1 || part.right() > other_rect.right()) {
if (part.y() < other_rect.y())
m_top_right = true;
if ((part.y() >= other_rect.y() && part.y() < other_rect.bottom()) || (part.y() <= other_rect.bottom() && part.bottom() > other_rect.y()))
if ((part.y() >= other_rect.y() && part.y() < other_rect.bottom() - 1) || (part.y() < other_rect.bottom() && part.bottom() - 1 > other_rect.y()))
m_right = true;
if (part.y() >= other_rect.bottom() || part.bottom() > other_rect.y())
if (part.y() >= other_rect.bottom() - 1 || part.bottom() - 1 > other_rect.y())
m_bottom_right = true;
}
}
@ -763,9 +743,9 @@ public:
{
if (is_empty())
return Side::None;
if (point.y() == y() || point.y() == bottom())
return (point.x() >= x() && point.x() <= right()) ? (point.y() == y() ? Side::Top : Side::Bottom) : Side::None;
if (point.x() == x() || point.x() == right())
if (point.y() == y() || point.y() == bottom() - 1)
return (point.x() >= x() && point.x() < right()) ? (point.y() == y() ? Side::Top : Side::Bottom) : Side::None;
if (point.x() == x() || point.x() == right() - 1)
return (point.y() > y() && point.y() < bottom()) ? (point.x() == x() ? Side::Left : Side::Right) : Side::None;
return Side::None;
}
@ -778,7 +758,7 @@ public:
case Side::Left:
// Return the area in other that is to the left of this rect
if (other.x() < x()) {
if (other.right() >= x())
if (other.right() > x())
return { other.location(), { x() - other.x(), other.height() } };
else
return other;
@ -787,7 +767,7 @@ public:
case Side::Top:
// Return the area in other that is above this rect
if (other.y() < y()) {
if (other.bottom() >= y())
if (other.bottom() > y())
return { other.location(), { other.width(), y() - other.y() } };
else
return other;
@ -795,18 +775,18 @@ public:
break;
case Side::Right:
// Return the area in other that is to the right of this rect
if (other.right() >= x()) {
if (other.x() <= right())
return { { right() + 1, other.y() }, { other.width() - (right() - other.x()), other.height() } };
if (other.right() > x()) {
if (other.x() < right())
return { { right(), other.y() }, { other.width() - (right() - 1 - other.x()), other.height() } };
else
return other;
}
break;
case Side::Bottom:
// Return the area in other that is below this rect
if (other.bottom() >= y()) {
if (other.y() <= bottom())
return { { other.x(), bottom() + 1 }, { other.width(), other.height() - (bottom() - other.y()) } };
if (other.bottom() > y()) {
if (other.y() < bottom())
return { { other.x(), bottom() }, { other.width(), other.height() - (bottom() - 1 - other.y()) } };
else
return other;
}
@ -878,10 +858,10 @@ public:
return false;
if (intersects(other))
return false;
if (other.x() + other.width() == x() || other.x() == x() + width())
return max(top(), other.top()) <= min(bottom(), other.bottom());
if (other.y() + other.height() == y() || other.y() == y() + height())
return max(left(), other.left()) <= min(right(), other.right());
if (other.right() == x() || other.x() == right())
return max(top(), other.top()) < min(bottom(), other.bottom());
if (other.bottom() == y() || other.y() == bottom())
return max(left(), other.left()) < min(right(), other.right());
return false;
}
@ -923,7 +903,7 @@ public:
set_location(other.location());
return;
case TextAlignment::TopRight:
set_x(other.x() + other.width() - width());
set_x(other.right() - width());
set_y(other.y());
return;
case TextAlignment::CenterLeft:
@ -931,20 +911,20 @@ public:
center_vertically_within(other);
return;
case TextAlignment::CenterRight:
set_x(other.x() + other.width() - width());
set_x(other.right() - width());
center_vertically_within(other);
return;
case TextAlignment::BottomCenter:
center_horizontally_within(other);
set_y(other.y() + other.height() - height());
set_y(other.bottom() - height());
return;
case TextAlignment::BottomLeft:
set_x(other.x());
set_y(other.y() + other.height() - height());
set_y(other.bottom() - height());
return;
case TextAlignment::BottomRight:
set_x(other.x() + other.width() - width());
set_y(other.y() + other.height() - height());
set_x(other.right() - width());
set_y(other.bottom() - height());
return;
}
}
@ -1036,8 +1016,8 @@ using FloatRect = Rect<float>;
{
int x1 = floorf(float_rect.x());
int y1 = floorf(float_rect.y());
int x2 = ceilf(float_rect.x() + float_rect.width());
int y2 = ceilf(float_rect.y() + float_rect.height());
int x2 = ceilf(float_rect.right());
int y2 = ceilf(float_rect.bottom());
return Gfx::IntRect::from_two_points({ x1, y1 }, { x2, y2 });
}