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 - -Rect::RelativeLocation::RelativeLocation(Rect const& base_rect, Rect 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 -Vector, 2> Rect::intersected(Line const& line) const -{ - if (is_empty()) - return {}; - Vector, 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 -float Rect::center_point_distance_to(Rect const& other) const -{ - return Line { center(), other.center() }.length(); -} - -template -Vector, 2> Rect::closest_outside_center_points(Rect 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 -float Rect::outside_center_point_distance_to(Rect 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 -Rect Rect::constrained_to(Rect 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 -Rect Rect::aligned_within(Size const& rect_size, Point const& align_at, TextAlignment alignment) const -{ - if (rect_size.is_empty()) - return {}; - if (!size().contains(rect_size)) - return {}; - if (!contains(align_at)) - return {}; - - Rect 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 -Point Rect::closest_to(Point const& point) const -{ - if (is_empty()) - return {}; - Optional> closest_point; - float closest_distance = 0.0; - auto check_distance = [&](Line 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 -void Rect::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; - } - - 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 -Rect Rect::united(Rect const& other) const -{ - if (is_null()) - return other; - if (other.is_null()) - return *this; - Rect 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 -Vector, 4> Rect::shatter(Rect const& hammer) const -{ - Vector, 4> pieces; - if (!intersects(hammer)) { - pieces.unchecked_append(*this); - return pieces; - } - Rect top_shard { - x(), - y(), - width(), - hammer.y() - y() - }; - Rect bottom_shard { - x(), - hammer.y() + hammer.height(), - width(), - (y() + height()) - (hammer.y() + hammer.height()) - }; - Rect left_shard { - x(), - max(hammer.y(), y()), - hammer.x() - x(), - min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y()) - }; - Rect 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 -void Rect::align_within(Rect 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 - * Copyright (c) 2021, Sam Atkins + * Copyright (c) 2021-2022, Sam Atkins * Copyright (c) 2022, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -427,7 +428,48 @@ public: return IterationDecision::Continue; } - [[nodiscard]] Vector, 4> shatter(Rect const& hammer) const; + [[nodiscard]] Vector, 4> shatter(Rect const& hammer) const + { + Vector, 4> pieces; + if (!intersects(hammer)) { + pieces.unchecked_append(*this); + return pieces; + } + Rect top_shard { + x(), + y(), + width(), + hammer.y() - y() + }; + Rect bottom_shard { + x(), + hammer.y() + hammer.height(), + width(), + (y() + height()) - (hammer.y() + hammer.height()) + }; + Rect left_shard { + x(), + max(hammer.y(), y()), + hammer.x() - x(), + min((hammer.y() + hammer.height()), (y() + height())) - max(hammer.y(), y()) + }; + Rect 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 [[nodiscard]] bool operator==(Rect const& other) const @@ -450,7 +492,24 @@ public: return *this; } - void intersect(Rect const&); + 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; + } + + 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 centered_on(Point const& center, Size const& size) { @@ -474,18 +533,175 @@ public: return intersection(*this, other); } - [[nodiscard]] Vector, 2> intersected(Line const&) const; - [[nodiscard]] float center_point_distance_to(Rect const&) const; - [[nodiscard]] Vector, 2> closest_outside_center_points(Rect const&) const; - [[nodiscard]] float outside_center_point_distance_to(Rect const&) const; - [[nodiscard]] Rect constrained_to(Rect const&) const; - [[nodiscard]] Rect aligned_within(Size const&, Point const&, TextAlignment = TextAlignment::Center) const; - [[nodiscard]] Point closest_to(Point const&) const; + [[nodiscard]] Vector, 2> intersected(Line const& line) const + { + if (is_empty()) + return {}; + Vector, 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 const& other) const + { + return Line { center(), other.center() }.length(); + } + + [[nodiscard]] Vector, 2> closest_outside_center_points(Rect 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 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 constrained_to(Rect 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 aligned_within(Size const& rect_size, Point 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 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 closest_to(Point const& point) const + { + if (is_empty()) + return {}; + Optional> closest_point; + float closest_distance = 0.0; + auto check_distance = [&](Line 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; - RelativeLocation(Rect const& base_rect, Rect const& other_rect); + RelativeLocation(Rect const& base_rect, Rect 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 united(Rect const&) const; + [[nodiscard]] Rect united(Rect const& other) const + { + if (is_null()) + return other; + if (other.is_null()) + return *this; + Rect 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 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&, TextAlignment); + void align_within(Rect 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 const& other) { diff --git a/Userland/Services/WindowServer/ConnectionFromClient.cpp b/Userland/Services/WindowServer/ConnectionFromClient.cpp index 1c3212bd36..64fd194428 100644 --- a/Userland/Services/WindowServer/ConnectionFromClient.cpp +++ b/Userland/Services/WindowServer/ConnectionFromClient.cpp @@ -686,7 +686,7 @@ void ConnectionFromClient::invalidate_rect(i32 window_id, Vector c } auto& window = *(*it).value; for (size_t i = 0; i < rects.size(); ++i) - window.request_update(rects[i].intersected({ {}, window.size() }), ignore_occlusion); + window.request_update(rects[i].intersected(Gfx::Rect { {}, window.size() }), ignore_occlusion); } void ConnectionFromClient::did_finish_painting(i32 window_id, Vector const& rects) diff --git a/Userland/Services/WindowServer/WindowFrame.cpp b/Userland/Services/WindowServer/WindowFrame.cpp index 241340e767..3dc2d3d0e4 100644 --- a/Userland/Services/WindowServer/WindowFrame.cpp +++ b/Userland/Services/WindowServer/WindowFrame.cpp @@ -349,7 +349,7 @@ void WindowFrame::PerScaleRenderedCache::paint(WindowFrame& frame, Gfx::Painter& auto top_bottom_height = frame_rect.height() - window_rect.height(); if (m_bottom_y > 0) { // We have a top piece - auto src_rect = rect.intersected({ frame_rect.location(), { frame_rect.width(), m_bottom_y } }); + auto src_rect = rect.intersected(Gfx::Rect { frame_rect.location(), { frame_rect.width(), m_bottom_y } }); if (!src_rect.is_empty()) painter.blit(src_rect.location(), *m_top_bottom, src_rect.translated(-frame_rect.location()), frame.opacity()); }