1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-06-01 02:38:13 +00:00

LibGfx: Add a Line class and a Rect<T>::RelativeLocation class

These helpers will be useful in preparation for supporting multiple
displays, e.g. to measure distances to other screens or figure out
where rectangles are located relative to each other.
This commit is contained in:
Tom 2021-06-13 06:13:06 -06:00 committed by Andreas Kling
parent 20c066b8e0
commit 499c33ae0c
4 changed files with 458 additions and 0 deletions

View file

@ -7,12 +7,183 @@
#include <AK/StdLibExtras.h>
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibGfx/Line.h>
#include <LibGfx/Rect.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
namespace Gfx {
template<typename T>
Rect<T>::RelativeLocation::RelativeLocation(Rect<T> const& base_rect, Rect<T> const& other_rect)
{
if (base_rect.is_empty() || other_rect.is_empty())
return;
auto parts = base_rect.shatter(other_rect);
for (auto& part : parts) {
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()))
m_left = true;
if (part.y() >= other_rect.bottom() || part.bottom() > other_rect.y())
m_bottom_left = true;
}
if (part.x() >= other_rect.x() || part.right() > other_rect.x()) {
if (part.y() < other_rect.y())
m_top = true;
if (part.y() >= other_rect.bottom() || part.bottom() > other_rect.bottom())
m_bottom = true;
}
if (part.x() >= other_rect.right() || 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()))
m_right = true;
if (part.y() >= other_rect.bottom() || part.bottom() > other_rect.y())
m_bottom_right = true;
}
}
}
template<typename T>
Vector<Point<T>, 2> Rect<T>::intersected(Line<T> const& line) const
{
if (is_empty())
return {};
Vector<Point<T>, 2> points;
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() });
if (points.size() == 2)
return points;
}
if (height() > 2) {
if (auto point = line.intersected({ { x(), y() + 1 }, { x(), bottom() - 1 } }); 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() });
}
return points;
}
template<typename T>
float Rect<T>::center_point_distance_to(Rect<T> const& other) const
{
return Line { center(), other.center() }.length();
}
template<typename T>
Vector<Point<T>, 2> Rect<T>::closest_outside_center_points(Rect<T> const& other) const
{
if (intersects(other))
return {};
Line centers_line { center(), other.center() };
auto points_this = intersected(centers_line);
VERIFY(points_this.size() == 1);
auto points_other = other.intersected(centers_line);
VERIFY(points_other.size() == 1);
return { points_this[0], points_other[0] };
}
template<typename T>
float Rect<T>::outside_center_point_distance_to(Rect<T> const& other) const
{
auto points = closest_outside_center_points(other);
if (points.is_empty())
return 0.0;
return Line { points[0], points[0] }.length();
}
template<typename T>
Rect<T> Rect<T>::constrained_to(Rect<T> const& constrain_rect) const
{
if (constrain_rect.contains(*this))
return *this;
T move_x = 0, move_y = 0;
if (right() > constrain_rect.right())
move_x = constrain_rect.right() - right();
if (bottom() > constrain_rect.bottom())
move_y = constrain_rect.bottom() - bottom();
if (x() < constrain_rect.x())
move_x = x() - constrain_rect.x();
if (y() < constrain_rect.y())
move_y = y() - constrain_rect.y();
auto rect = *this;
if (move_x != 0 || move_y != 0)
rect.translate_by(move_x, move_y);
return rect;
}
template<typename T>
Rect<T> Rect<T>::aligned_within(Size<T> const& rect_size, Point<T> const& align_at, TextAlignment alignment) const
{
if (rect_size.is_empty())
return {};
if (!size().contains(rect_size))
return {};
if (!contains(align_at))
return {};
Rect<T> rect;
switch (alignment) {
case TextAlignment::TopLeft:
rect = { align_at, rect_size };
break;
case TextAlignment::CenterLeft:
rect = { { align_at.x(), align_at.y() - rect_size.height() / 2 }, rect_size };
break;
case TextAlignment::Center:
rect = { { align_at.x() - rect_size.width() / 2, align_at.y() - rect_size.height() / 2 }, rect_size };
break;
case TextAlignment::CenterRight:
rect = { { align_at.x() - rect_size.width() / 2, align_at.y() }, rect_size };
break;
case TextAlignment::TopRight:
rect = { { align_at.x() - rect_size.width(), align_at.y() }, rect_size };
break;
case TextAlignment::BottomLeft:
rect = { { align_at.x(), align_at.y() - rect_size.width() }, rect_size };
break;
case TextAlignment::BottomRight:
rect = { { align_at.x() - rect_size.width(), align_at.y() - rect_size.width() }, rect_size };
break;
}
return rect.constrained_to(*this);
}
template<typename T>
Point<T> Rect<T>::closest_to(Point<T> const& point) const
{
if (is_empty())
return {};
Optional<Point<T>> closest_point;
float closest_distance = 0.0;
auto check_distance = [&](const Line<T>& line) {
auto point_on_line = line.closest_to(point);
auto distance = Line { point_on_line, point }.length();
if (!closest_point.has_value() || distance < closest_distance) {
closest_point = point_on_line;
closest_distance = distance;
}
};
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 } });
}
VERIFY(closest_point.has_value());
VERIFY(side(closest_point.value()) != Side::None);
return closest_point.value();
}
template<typename T>
void Rect<T>::intersect(Rect<T> const& other)
{