diff --git a/UI/Qt/BrowserWindow.cpp b/UI/Qt/BrowserWindow.cpp index f8106c8791..297e1065c6 100644 --- a/UI/Qt/BrowserWindow.cpp +++ b/UI/Qt/BrowserWindow.cpp @@ -64,6 +64,8 @@ static constexpr auto WINDOW_DRAG_REGION_PROPERTY = "LadybirdWindowDragRegion"; #if defined(AK_OS_MACOS) static constexpr qreal WINDOW_CORNER_RADIUS = 12.0; #endif +static constexpr int WINDOW_RESIZE_BORDER_WIDTH = 6; +static constexpr int WINDOW_RESIZE_CORNER_WIDTH = WINDOW_RESIZE_BORDER_WIDTH * 2; static bool should_use_screen_signal_for_dpi_changes() { @@ -243,6 +245,7 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, IsPopupWindow setWindowIcon(app_icon()); qApp->installEventFilter(this); update_window_corners(); + update_appkit_window_resizability(); update_window_border(); update_tabs_display(); @@ -992,6 +995,7 @@ void BrowserWindow::update_window_decoration_state() } } + update_appkit_window_resizability(); update_menu_bar_visibility(); update_window_border(); } @@ -1160,6 +1164,7 @@ bool BrowserWindow::event(QEvent* event) #if defined(AK_OS_MACOS) QTimer::singleShot(0, this, [this] { update_window_corners(); + update_appkit_window_resizability(); }); #endif } else if (platform_surface_event->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) { @@ -1169,10 +1174,17 @@ bool BrowserWindow::event(QEvent* event) if (event->type() == QEvent::ScreenChangeInternal) screen_changed(screen()); - if (event->type() == QEvent::WindowActivate) + if (event->type() == QEvent::WindowActivate) { Application::the().set_active_window(*this); - else if (event->type() == QEvent::WindowDeactivate || event->type() == QEvent::Hide) + QTimer::singleShot(0, this, [this] { + refresh_resize_cursor_at_current_position(true); + }); + QTimer::singleShot(50, this, [this] { + refresh_resize_cursor_at_current_position(true); + }); + } else if (event->type() == QEvent::WindowDeactivate || event->type() == QEvent::Hide) { clear_resize_cursor(); + } return QMainWindow::event(event); } @@ -1185,6 +1197,18 @@ bool BrowserWindow::eventFilter(QObject* object, QEvent* event) if (!uses_client_side_decorations()) return QMainWindow::eventFilter(object, event); + if (m_is_resizing_window) { + if (event->type() == QEvent::MouseMove) { + auto* mouse_event = static_cast(event); + update_window_resize(mouse_event->globalPosition().toPoint()); + return true; + } + if (event->type() == QEvent::MouseButtonRelease) { + finish_window_resize(); + return true; + } + } + auto const is_button = qobject_cast(object) != nullptr; if (is_button && (event->type() == QEvent::Enter || event->type() == QEvent::MouseMove || event->type() == QEvent::Leave)) { @@ -1215,11 +1239,18 @@ bool BrowserWindow::eventFilter(QObject* object, QEvent* event) return QMainWindow::eventFilter(object, event); auto position = widget->mapTo(this, mouse_event->position().toPoint()); + auto edges = resize_edges_for_position(position); if (event->type() == QEvent::MouseButtonPress && !isMaximized() && !isFullScreen()) { - auto edges = resize_edges_for_position(position); - auto* handle = windowHandle(); - if (edges != Qt::Edges {} && handle && handle->startSystemResize(edges)) - return true; + if (edges != Qt::Edges {}) { +#if defined(AK_OS_MACOS) + if (start_window_resize(edges, mouse_event->globalPosition().toPoint())) + return true; +#else + auto* handle = windowHandle(); + if (handle && handle->startSystemResize(edges)) + return true; +#endif + } } auto is_empty_window_drag_region = widget->property(WINDOW_DRAG_REGION_PROPERTY).toBool() @@ -1248,18 +1279,62 @@ bool BrowserWindow::eventFilter(QObject* object, QEvent* event) return QMainWindow::eventFilter(object, event); } +bool BrowserWindow::position_is_in_rounded_corner_cutout(QPoint const& position) const +{ +#if defined(AK_OS_MACOS) + auto should_use_rounded_corners = WebView::Application::settings().config_variable_as_bool(WebView::ConfigVariableID::UseRoundedWindowCorners); + if (!should_use_rounded_corners || isFullScreen()) + return false; + + auto const radius = WINDOW_CORNER_RADIUS; + auto const x = static_cast(position.x()); + auto const y = static_cast(position.y()); + auto const right = static_cast(width()); + auto const bottom = static_cast(height()); + + auto is_outside_corner_arc = [radius](qreal dx, qreal dy) { + return dx * dx + dy * dy > radius * radius; + }; + + auto in_cutout = false; + if (x < radius && y < radius) + in_cutout = is_outside_corner_arc(radius - x, radius - y); + else if (x >= right - radius && y < radius) + in_cutout = is_outside_corner_arc(x - (right - radius), radius - y); + else if (x < radius && y >= bottom - radius) + in_cutout = is_outside_corner_arc(radius - x, y - (bottom - radius)); + else if (x >= right - radius && y >= bottom - radius) + in_cutout = is_outside_corner_arc(x - (right - radius), y - (bottom - radius)); + + return in_cutout; +#else + (void)position; +#endif + return false; +} + Qt::Edges BrowserWindow::resize_edges_for_position(QPoint const& position) const { - static constexpr int resize_border_width = 6; + if (position_is_in_rounded_corner_cutout(position)) + return {}; Qt::Edges edges; - if (position.x() <= resize_border_width) + auto in_left_resize_edge = position.x() <= WINDOW_RESIZE_BORDER_WIDTH; + auto in_right_resize_edge = position.x() >= width() - WINDOW_RESIZE_BORDER_WIDTH; + auto in_top_resize_edge = position.y() <= WINDOW_RESIZE_BORDER_WIDTH; + auto in_bottom_resize_edge = position.y() >= height() - WINDOW_RESIZE_BORDER_WIDTH; + auto in_left_resize_corner = position.x() <= WINDOW_RESIZE_CORNER_WIDTH; + auto in_right_resize_corner = position.x() >= width() - WINDOW_RESIZE_CORNER_WIDTH; + auto in_top_resize_corner = position.y() <= WINDOW_RESIZE_CORNER_WIDTH; + auto in_bottom_resize_corner = position.y() >= height() - WINDOW_RESIZE_CORNER_WIDTH; + + if (in_left_resize_edge || (in_left_resize_corner && (in_top_resize_corner || in_bottom_resize_corner))) edges |= Qt::LeftEdge; - if (position.x() >= width() - resize_border_width) + if (in_right_resize_edge || (in_right_resize_corner && (in_top_resize_corner || in_bottom_resize_corner))) edges |= Qt::RightEdge; - if (position.y() <= resize_border_width) + if (in_top_resize_edge || (in_top_resize_corner && (in_left_resize_corner || in_right_resize_corner))) edges |= Qt::TopEdge; - if (position.y() >= height() - resize_border_width) + if (in_bottom_resize_edge || (in_bottom_resize_corner && (in_left_resize_corner || in_right_resize_corner))) edges |= Qt::BottomEdge; return edges; @@ -1282,6 +1357,56 @@ Optional BrowserWindow::resize_cursor_for_edges(Qt::Edges edges return {}; } +bool BrowserWindow::start_window_resize(Qt::Edges edges, QPoint const& global_position) +{ + if (edges == Qt::Edges {} || isMaximized() || isFullScreen()) + return false; + + m_is_resizing_window = true; + m_resize_edges = edges; + m_resize_start_global_position = global_position; + m_resize_start_geometry = geometry(); + grabMouse(); + return true; +} + +void BrowserWindow::update_window_resize(QPoint const& global_position) +{ + if (!m_is_resizing_window) + return; + + auto delta = global_position - m_resize_start_global_position; + auto new_geometry = m_resize_start_geometry; + + if (m_resize_edges & Qt::LeftEdge) { + auto new_width = qBound(minimumWidth(), m_resize_start_geometry.width() - delta.x(), maximumWidth()); + new_geometry.setX(m_resize_start_geometry.right() - new_width + 1); + } else if (m_resize_edges & Qt::RightEdge) { + auto new_width = qBound(minimumWidth(), m_resize_start_geometry.width() + delta.x(), maximumWidth()); + new_geometry.setWidth(new_width); + } + + if (m_resize_edges & Qt::TopEdge) { + auto new_height = qBound(minimumHeight(), m_resize_start_geometry.height() - delta.y(), maximumHeight()); + new_geometry.setY(m_resize_start_geometry.bottom() - new_height + 1); + } else if (m_resize_edges & Qt::BottomEdge) { + auto new_height = qBound(minimumHeight(), m_resize_start_geometry.height() + delta.y(), maximumHeight()); + new_geometry.setHeight(new_height); + } + + setGeometry(new_geometry); +} + +void BrowserWindow::finish_window_resize() +{ + if (!m_is_resizing_window) + return; + + m_is_resizing_window = false; + m_resize_edges = {}; + releaseMouse(); +} + void BrowserWindow::update_resize_cursor(QPoint const& position) { if (!uses_client_side_decorations() || isMaximized() || isFullScreen() || !rect().contains(position)) { @@ -1303,6 +1428,25 @@ void BrowserWindow::update_resize_cursor(QPoint const& position) } } +void BrowserWindow::refresh_resize_cursor_at_current_position(bool force_reapply) +{ + auto position = mapFromGlobal(QCursor::pos()); + auto* child = childAt(position); + for (auto* object = child; object; object = qobject_cast(object->parent())) { + if (qobject_cast(object)) { + clear_resize_cursor(); + return; + } + } + + if (force_reapply && m_resize_cursor_active) { + QApplication::restoreOverrideCursor(); + m_resize_cursor_active = false; + } + + update_resize_cursor(position); +} + void BrowserWindow::clear_resize_cursor() { if (!m_resize_cursor_active) @@ -1373,6 +1517,13 @@ void BrowserWindow::update_window_corners() #endif } +void BrowserWindow::update_appkit_window_resizability() +{ +#if defined(AK_OS_MACOS) + set_appkit_window_resizable(*this, !uses_client_side_decorations()); +#endif +} + bool BrowserWindow::should_draw_window_border() const { #if defined(AK_OS_MACOS) diff --git a/UI/Qt/BrowserWindow.h b/UI/Qt/BrowserWindow.h index 85e55e905d..88f204704a 100644 --- a/UI/Qt/BrowserWindow.h +++ b/UI/Qt/BrowserWindow.h @@ -18,6 +18,7 @@ #include #include #include +#include #include class QPropertyAnimation; @@ -167,11 +168,17 @@ private: void uninitialize_tab(Tab*); void set_current_tab(Tab* tab); + bool position_is_in_rounded_corner_cutout(QPoint const&) const; Qt::Edges resize_edges_for_position(QPoint const&) const; Optional resize_cursor_for_edges(Qt::Edges) const; + bool start_window_resize(Qt::Edges, QPoint const& global_position); + void update_window_resize(QPoint const& global_position); + void finish_window_resize(); void update_resize_cursor(QPoint const&); + void refresh_resize_cursor_at_current_position(bool force_reapply = false); void clear_resize_cursor(); void update_window_corners(); + void update_appkit_window_resizability(); bool should_draw_window_border() const; void update_window_border(); @@ -232,6 +239,10 @@ private: bool m_restore_to_maximized { false }; bool m_should_record_closed_window_on_close { true }; bool m_resize_cursor_active { false }; + bool m_is_resizing_window { false }; + Qt::Edges m_resize_edges {}; + QPoint m_resize_start_global_position; + QRect m_resize_start_geometry; }; } diff --git a/UI/Qt/MacWindow.h b/UI/Qt/MacWindow.h index 5105d44773..b64109de3b 100644 --- a/UI/Qt/MacWindow.h +++ b/UI/Qt/MacWindow.h @@ -20,6 +20,7 @@ namespace Ladybird { #if defined(AK_OS_MACOS) void set_rounded_window_corners(QWidget&, bool enabled, double radius, QColor const& background_color); +void set_appkit_window_resizable(QWidget&, bool enabled); void install_always_active_window_control_hover_tracking(QWidget&, void (*hover_changed)(QWidget*)); void install_appkit_event_capture(); void make_appkit_window_first_responder(QWidget&); diff --git a/UI/Qt/MacWindow.mm b/UI/Qt/MacWindow.mm index a28e603ce2..68c69d530d 100644 --- a/UI/Qt/MacWindow.mm +++ b/UI/Qt/MacWindow.mm @@ -185,6 +185,22 @@ void set_rounded_window_corners(QWidget& widget, bool enabled, double radius, QC content_view.layer.backgroundColor = cg_color_from_qcolor(background_color); } +void set_appkit_window_resizable(QWidget& widget, bool enabled) +{ + auto* view = reinterpret_cast(widget.winId()); + if (!view) + return; + + auto* window = view.window; + if (!window) + return; + + if (enabled) + window.styleMask |= NSWindowStyleMaskResizable; + else + window.styleMask &= ~NSWindowStyleMaskResizable; +} + void install_always_active_window_control_hover_tracking(QWidget& widget, void (*hover_changed)(QWidget*)) { static char tracker_key;