mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 15:27:35 +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:
parent
20c066b8e0
commit
499c33ae0c
4 changed files with 458 additions and 0 deletions
|
@ -16,6 +16,10 @@ class Emoji;
|
|||
class Font;
|
||||
class GlyphBitmap;
|
||||
class ImageDecoder;
|
||||
|
||||
template<typename T>
|
||||
class Line;
|
||||
|
||||
class Painter;
|
||||
class Palette;
|
||||
class PaletteImpl;
|
||||
|
@ -34,6 +38,9 @@ class Size;
|
|||
template<typename T>
|
||||
class Rect;
|
||||
|
||||
using IntLine = Line<int>;
|
||||
using FloatLine = Line<float>;
|
||||
|
||||
using IntRect = Rect<int>;
|
||||
using FloatRect = Rect<float>;
|
||||
|
||||
|
|
155
Userland/Libraries/LibGfx/Line.h
Normal file
155
Userland/Libraries/LibGfx/Line.h
Normal file
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (c) 2021, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/StdLibExtras.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Point.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
template<typename T>
|
||||
class Line {
|
||||
public:
|
||||
Line() { }
|
||||
|
||||
Line(Point<T> a, Point<T> b)
|
||||
: m_a(a)
|
||||
, m_b(b)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
Line(U a, U b)
|
||||
: m_a(a)
|
||||
, m_b(b)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
explicit Line(Line<U> const& other)
|
||||
: m_a(other.a())
|
||||
, m_b(other.b())
|
||||
{
|
||||
}
|
||||
|
||||
bool intersects(Line const& other) const
|
||||
{
|
||||
return intersected(other).has_value();
|
||||
}
|
||||
|
||||
Optional<Point<T>> intersected(Line const& other) const
|
||||
{
|
||||
auto cross_product = [](Point<T> const& p1, Point<T> const& p2) {
|
||||
return p1.x() * p2.y() - p1.y() * p2.x();
|
||||
};
|
||||
auto r = m_b - m_a;
|
||||
auto s = other.m_b - other.m_a;
|
||||
auto delta_a = other.m_a - m_a;
|
||||
auto num = cross_product(delta_a, r);
|
||||
auto denom = cross_product(r, s);
|
||||
if (denom == 0) {
|
||||
if (num == 0) {
|
||||
// Lines are collinear, check if line ends are touching
|
||||
if (m_a == other.m_a || m_a == other.m_b)
|
||||
return m_a;
|
||||
if (m_b == other.m_a || m_b == other.m_b)
|
||||
return m_b;
|
||||
// Check if they're overlapping
|
||||
if (!(m_b.x() - m_a.x() < 0 && m_b.x() - other.m_a.x() < 0 && other.m_b.x() - m_a.x() && other.m_b.x() - other.m_a.x())) {
|
||||
// Overlapping
|
||||
// TODO find center point?
|
||||
}
|
||||
if (!(m_b.y() - m_a.y() < 0 && m_b.y() - other.m_a.y() < 0 && other.m_b.y() - m_a.y() && other.m_b.y() - other.m_a.y())) {
|
||||
// Overlapping
|
||||
// TODO find center point?
|
||||
}
|
||||
return {};
|
||||
} else {
|
||||
// Lines are parallel and not intersecting
|
||||
return {};
|
||||
}
|
||||
}
|
||||
auto u = static_cast<float>(num) / static_cast<float>(denom);
|
||||
if (u < 0.0f || u > 1.0f) {
|
||||
// Lines are not parallel and don't intersect
|
||||
return {};
|
||||
}
|
||||
auto t = static_cast<float>(cross_product(delta_a, s)) / static_cast<float>(denom);
|
||||
if (t < 0.0f || t > 1.0f) {
|
||||
// Lines are not parallel and don't intersect
|
||||
return {};
|
||||
}
|
||||
// TODO: round if we're dealing with int
|
||||
return Point<T> { m_a.x() + static_cast<T>(t * r.x()), m_a.y() + static_cast<T>(t * r.y()) };
|
||||
}
|
||||
|
||||
float length() const
|
||||
{
|
||||
return m_a.distance_from(m_b);
|
||||
}
|
||||
|
||||
Point<T> closest_to(Point<T> const& point) const
|
||||
{
|
||||
if (m_a == m_b)
|
||||
return m_a;
|
||||
auto delta_a = point.x() - m_a.x();
|
||||
auto delta_b = point.y() - m_a.y();
|
||||
auto delta_c = m_b.x() - m_a.x();
|
||||
auto delta_d = m_b.y() - m_a.y();
|
||||
auto len_sq = delta_c * delta_c + delta_d * delta_d;
|
||||
float param = -1.0;
|
||||
if (len_sq != 0)
|
||||
param = static_cast<float>(delta_a * delta_c + delta_b * delta_d) / static_cast<float>(len_sq);
|
||||
if (param < 0)
|
||||
return m_a;
|
||||
if (param > 1)
|
||||
return m_b;
|
||||
// TODO: round if we're dealing with int
|
||||
return { static_cast<T>(m_a.x() + param * delta_c), static_cast<T>(m_a.y() + param * delta_d) };
|
||||
}
|
||||
|
||||
Line<T> shortest_line_to(Point<T> const& point) const
|
||||
{
|
||||
return { closest_to(point), point };
|
||||
}
|
||||
|
||||
float distance_to(Point<T> const& point) const
|
||||
{
|
||||
return shortest_line_to(point).length();
|
||||
}
|
||||
|
||||
Point<T> const& a() const { return m_a; }
|
||||
Point<T> const& b() const { return m_b; }
|
||||
|
||||
void set_a(Point<T> const& a) { m_a = a; }
|
||||
void set_b(Point<T> const& b) { m_b = b; }
|
||||
|
||||
String to_string() const;
|
||||
|
||||
private:
|
||||
Point<T> m_a;
|
||||
Point<T> m_b;
|
||||
};
|
||||
|
||||
template<>
|
||||
inline String IntLine::to_string() const
|
||||
{
|
||||
return String::formatted("[{},{} -> {}x{}]", m_a.x(), m_a.y(), m_b.x(), m_b.y());
|
||||
}
|
||||
|
||||
template<>
|
||||
inline String FloatLine::to_string() const
|
||||
{
|
||||
return String::formatted("[{},{} {}x{}]", m_a.x(), m_a.y(), m_b.x(), m_b.y());
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -444,6 +444,131 @@ public:
|
|||
return intersection(*this, other);
|
||||
}
|
||||
|
||||
[[nodiscard]] Vector<Point<T>, 2> intersected(Line<T> const&) const;
|
||||
[[nodiscard]] float center_point_distance_to(Rect<T> const&) const;
|
||||
[[nodiscard]] Vector<Point<T>, 2> closest_outside_center_points(Rect<T> const&) const;
|
||||
[[nodiscard]] float outside_center_point_distance_to(Rect<T> const&) const;
|
||||
[[nodiscard]] Rect<T> constrained_to(Rect<T> const&) const;
|
||||
[[nodiscard]] Rect<T> aligned_within(Size<T> const&, Point<T> const&, TextAlignment = TextAlignment::Center) const;
|
||||
[[nodiscard]] Point<T> closest_to(Point<T> const&) const;
|
||||
|
||||
class RelativeLocation {
|
||||
friend class Rect<T>;
|
||||
|
||||
RelativeLocation(Rect<T> const& base_rect, Rect<T> const& other_rect);
|
||||
|
||||
public:
|
||||
RelativeLocation() = default;
|
||||
|
||||
bool top_left() const { return m_top_left; }
|
||||
bool top() const { return m_top; }
|
||||
bool top_right() const { return m_top_right; }
|
||||
bool left() const { return m_left; }
|
||||
bool right() const { return m_right; }
|
||||
bool bottom_left() const { return m_bottom_left; }
|
||||
bool bottom() const { return m_bottom; }
|
||||
bool bottom_right() const { return m_bottom_right; }
|
||||
bool anywhere_above() const { return m_top_left || m_top || m_top_right; }
|
||||
bool anywhere_below() const { return m_bottom_left || m_bottom || m_bottom_right; }
|
||||
bool anywhere_left() const { return m_top_left || m_left || m_bottom_left; }
|
||||
bool anywhere_right() const { return m_top_right || m_right || m_bottom_right; }
|
||||
|
||||
private:
|
||||
bool m_top_left : 1 { false };
|
||||
bool m_top : 1 { false };
|
||||
bool m_top_right : 1 { false };
|
||||
bool m_left : 1 { false };
|
||||
bool m_right : 1 { false };
|
||||
bool m_bottom_left : 1 { false };
|
||||
bool m_bottom : 1 { false };
|
||||
bool m_bottom_right : 1 { false };
|
||||
};
|
||||
[[nodiscard]] RelativeLocation relative_location_to(Rect<T> const& other) const
|
||||
{
|
||||
return RelativeLocation(*this, other);
|
||||
}
|
||||
|
||||
enum class Side {
|
||||
None = 0,
|
||||
Left,
|
||||
Top,
|
||||
Right,
|
||||
Bottom
|
||||
};
|
||||
[[nodiscard]] Side side(Point<T> const& point) const
|
||||
{
|
||||
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())
|
||||
return (point.y() > y() && point.y() < bottom()) ? (point.x() == x() ? Side::Left : Side::Right) : Side::None;
|
||||
return Side::None;
|
||||
}
|
||||
|
||||
[[nodiscard]] Rect<T> rect_on_side(Side side, Rect<T> const& other) const
|
||||
{
|
||||
switch (side) {
|
||||
case Side::None:
|
||||
break;
|
||||
case Side::Left:
|
||||
// Return the area in other that is to the left of this rect
|
||||
if (other.x() < x()) {
|
||||
if (other.right() >= x())
|
||||
return { other.location(), { x() - other.x(), other.height() } };
|
||||
else
|
||||
return other;
|
||||
}
|
||||
break;
|
||||
case Side::Top:
|
||||
// Return the area in other that is above this rect
|
||||
if (other.y() < y()) {
|
||||
if (other.bottom() >= y())
|
||||
return { other.location(), { other.width(), y() - other.y() } };
|
||||
else
|
||||
return other;
|
||||
}
|
||||
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() } };
|
||||
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()) } };
|
||||
else
|
||||
return other;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_adjacent(Rect<T> const& other) const
|
||||
{
|
||||
if (is_empty() || other.is_empty())
|
||||
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());
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] static Rect<T> centered_at(Point<T> const& point, Size<T> const& size)
|
||||
{
|
||||
return { { point.x() - size.width() / 2, point.y() - size.height() / 2 }, size };
|
||||
}
|
||||
|
||||
[[nodiscard]] Rect<T> united(Rect<T> const&) const;
|
||||
|
||||
[[nodiscard]] Point<T> top_left() const { return { left(), top() }; }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue