diff options
author | Sam Atkins <atkinssj@serenityos.org> | 2022-10-19 19:55:34 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-10-22 18:17:58 +0200 |
commit | 732aa2c5c720cf9ddf35cafe71f4c36e53205e15 (patch) | |
tree | 363eccb9f5910657312b0a084716bf2649b2427b /Userland/Libraries/LibGfx | |
parent | ae950816b572b173901451f2ea3292dcac341b73 (diff) | |
download | serenity-732aa2c5c720cf9ddf35cafe71f4c36e53205e15.zip |
LibGfx: Make `Rect<T>` methods work when `T` is not `int` or `float`
Putting the implementations in the .cpp file meant that they only
existed for `IntRect` and `FloatRect`, since those were instantiated at
the bottom of the file. Now they work for other types. :^)
A couple of places in WindowServer had to be modified to disambiguate
between the two `Rect::intersected()` overloads.
Co-authored-by: davidot <davidot@serenityos.org>
Diffstat (limited to 'Userland/Libraries/LibGfx')
-rw-r--r-- | Userland/Libraries/LibGfx/Rect.cpp | 295 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/Rect.h | 291 |
2 files changed, 278 insertions, 308 deletions
diff --git a/Userland/Libraries/LibGfx/Rect.cpp b/Userland/Libraries/LibGfx/Rect.cpp index 5f99fe2842..c278834ce0 100644 --- a/Userland/Libraries/LibGfx/Rect.cpp +++ b/Userland/Libraries/LibGfx/Rect.cpp @@ -14,301 +14,6 @@ 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::TopCenter: - rect = { { align_at.x() - rect_size.width() / 2, align_at.y() }, rect_size }; - break; - case TextAlignment::TopLeft: - rect = { align_at, rect_size }; - break; - case TextAlignment::TopRight: - rect = { { align_at.x() - rect_size.width(), align_at.y() }, 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::BottomCenter: - rect = { { align_at.x() - rect_size.width() / 2, align_at.y() - rect_size.width() }, 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 = [&](Line<T> const& 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) -{ - 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; - } - - m_location.set_x(l); - m_location.set_y(t); - m_size.set_width((r - l) + 1); - m_size.set_height((b - t) + 1); -} - -template<typename T> -Rect<T> Rect<T>::united(Rect<T> const& other) const -{ - if (is_null()) - return other; - if (other.is_null()) - return *this; - Rect<T> rect; - rect.set_left(min(left(), other.left())); - rect.set_top(min(top(), other.top())); - rect.set_right(max(right(), other.right())); - rect.set_bottom(max(bottom(), other.bottom())); - return rect; -} - -template<typename T> -Vector<Rect<T>, 4> Rect<T>::shatter(Rect<T> const& hammer) const -{ - Vector<Rect<T>, 4> pieces; - if (!intersects(hammer)) { - pieces.unchecked_append(*this); - return pieces; - } - Rect<T> top_shard { - x(), - y(), - width(), - hammer.y() - y() - }; - Rect<T> bottom_shard { - x(), - hammer.y() + hammer.height(), - width(), - (y() + height()) - (hammer.y() + hammer.height()) - }; - Rect<T> left_shard { - x(), - max(hammer.y(), y()), - hammer.x() - x(), - min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y()) - }; - Rect<T> right_shard { - hammer.x() + hammer.width(), - max(hammer.y(), y()), - right() - hammer.right(), - min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y()) - }; - if (!top_shard.is_empty()) - pieces.unchecked_append(top_shard); - if (!bottom_shard.is_empty()) - pieces.unchecked_append(bottom_shard); - if (!left_shard.is_empty()) - pieces.unchecked_append(left_shard); - if (!right_shard.is_empty()) - pieces.unchecked_append(right_shard); - - return pieces; -} - -template<typename T> -void Rect<T>::align_within(Rect<T> const& other, TextAlignment alignment) -{ - switch (alignment) { - case TextAlignment::Center: - center_within(other); - return; - case TextAlignment::TopCenter: - set_x(other.x() + other.width() / 2); - return; - case TextAlignment::TopLeft: - set_location(other.location()); - return; - case TextAlignment::TopRight: - set_x(other.x() + other.width() - width()); - set_y(other.y()); - return; - case TextAlignment::CenterLeft: - set_x(other.x()); - center_vertically_within(other); - return; - case TextAlignment::CenterRight: - set_x(other.x() + other.width() - width()); - center_vertically_within(other); - return; - case TextAlignment::BottomCenter: - set_x(other.x() + other.width() / 2); - set_y(other.y() + other.height() - height()); - return; - case TextAlignment::BottomLeft: - set_x(other.x()); - set_y(other.y() + other.height() - height()); - return; - case TextAlignment::BottomRight: - set_x(other.x() + other.width() - width()); - set_y(other.y() + other.height() - height()); - return; - } -} - template<> String IntRect::to_string() const { diff --git a/Userland/Libraries/LibGfx/Rect.h b/Userland/Libraries/LibGfx/Rect.h index fcee570127..427a24e180 100644 --- a/Userland/Libraries/LibGfx/Rect.h +++ b/Userland/Libraries/LibGfx/Rect.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> - * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org> + * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org> * Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl> * * SPDX-License-Identifier: BSD-2-Clause @@ -10,6 +10,7 @@ #include <AK/Format.h> #include <LibGfx/AffineTransform.h> +#include <LibGfx/Line.h> #include <LibGfx/Orientation.h> #include <LibGfx/Point.h> #include <LibGfx/Size.h> @@ -427,7 +428,48 @@ public: return IterationDecision::Continue; } - [[nodiscard]] Vector<Rect<T>, 4> shatter(Rect<T> const& hammer) const; + [[nodiscard]] Vector<Rect<T>, 4> shatter(Rect<T> const& hammer) const + { + Vector<Rect<T>, 4> pieces; + if (!intersects(hammer)) { + pieces.unchecked_append(*this); + return pieces; + } + Rect<T> top_shard { + x(), + y(), + width(), + hammer.y() - y() + }; + Rect<T> bottom_shard { + x(), + hammer.y() + hammer.height(), + width(), + (y() + height()) - (hammer.y() + hammer.height()) + }; + Rect<T> left_shard { + x(), + max(hammer.y(), y()), + hammer.x() - x(), + min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y()) + }; + Rect<T> right_shard { + hammer.x() + hammer.width(), + max(hammer.y(), y()), + right() - hammer.right(), + min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y()) + }; + if (!top_shard.is_empty()) + pieces.unchecked_append(top_shard); + if (!bottom_shard.is_empty()) + pieces.unchecked_append(bottom_shard); + if (!left_shard.is_empty()) + pieces.unchecked_append(left_shard); + if (!right_shard.is_empty()) + pieces.unchecked_append(right_shard); + + return pieces; + } template<class U> [[nodiscard]] bool operator==(Rect<U> const& other) const @@ -450,7 +492,24 @@ public: return *this; } - void intersect(Rect<T> const&); + void intersect(Rect<T> 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; + } + + m_location.set_x(l); + m_location.set_y(t); + m_size.set_width((r - l) + 1); + m_size.set_height((b - t) + 1); + } [[nodiscard]] static Rect<T> centered_on(Point<T> const& center, Size<T> const& size) { @@ -474,18 +533,175 @@ 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; + [[nodiscard]] Vector<Point<T>, 2> 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; + } + + [[nodiscard]] float center_point_distance_to(Rect<T> const& other) const + { + return Line { center(), other.center() }.length(); + } + + [[nodiscard]] Vector<Point<T>, 2> 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] }; + } + + [[nodiscard]] float 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(); + } + + [[nodiscard]] 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; + } + + [[nodiscard]] Rect<T> aligned_within(Size<T> const& rect_size, Point<T> const& align_at, TextAlignment alignment = TextAlignment::Center) 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::TopCenter: + rect = { { align_at.x() - rect_size.width() / 2, align_at.y() }, rect_size }; + break; + case TextAlignment::TopLeft: + rect = { align_at, rect_size }; + break; + case TextAlignment::TopRight: + rect = { { align_at.x() - rect_size.width(), align_at.y() }, 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::BottomCenter: + rect = { { align_at.x() - rect_size.width() / 2, align_at.y() - rect_size.width() }, 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); + } + + [[nodiscard]] Point<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 = [&](Line<T> const& 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(); + } class RelativeLocation { friend class Rect<T>; - RelativeLocation(Rect<T> const& base_rect, Rect<T> const& other_rect); + 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; + } + } + } public: RelativeLocation() = default; @@ -656,14 +872,63 @@ public: return { { point.x() - size.width() / 2, point.y() - size.height() / 2 }, size }; } - [[nodiscard]] Rect<T> united(Rect<T> const&) const; + [[nodiscard]] Rect<T> united(Rect<T> const& other) const + { + if (is_null()) + return other; + if (other.is_null()) + return *this; + Rect<T> rect; + rect.set_left(min(left(), other.left())); + rect.set_top(min(top(), other.top())); + rect.set_right(max(right(), other.right())); + rect.set_bottom(max(bottom(), other.bottom())); + return rect; + } [[nodiscard]] Point<T> top_left() const { return { left(), top() }; } [[nodiscard]] Point<T> top_right() const { return { right(), top() }; } [[nodiscard]] Point<T> bottom_left() const { return { left(), bottom() }; } [[nodiscard]] Point<T> bottom_right() const { return { right(), bottom() }; } - void align_within(Rect<T> const&, TextAlignment); + void align_within(Rect<T> const& other, TextAlignment alignment) + { + switch (alignment) { + case TextAlignment::Center: + center_within(other); + return; + case TextAlignment::TopCenter: + set_x(other.x() + other.width() / 2); + return; + case TextAlignment::TopLeft: + set_location(other.location()); + return; + case TextAlignment::TopRight: + set_x(other.x() + other.width() - width()); + set_y(other.y()); + return; + case TextAlignment::CenterLeft: + set_x(other.x()); + center_vertically_within(other); + return; + case TextAlignment::CenterRight: + set_x(other.x() + other.width() - width()); + center_vertically_within(other); + return; + case TextAlignment::BottomCenter: + set_x(other.x() + other.width() / 2); + set_y(other.y() + other.height() - height()); + return; + case TextAlignment::BottomLeft: + set_x(other.x()); + set_y(other.y() + other.height() - height()); + return; + case TextAlignment::BottomRight: + set_x(other.x() + other.width() - width()); + set_y(other.y() + other.height() - height()); + return; + } + } void center_within(Rect<T> const& other) { |