UI/Qt: Resize frameless macOS windows manually
Disable AppKit's native resize handling for client-side decorated macOS windows so AppKit does not show a resize cursor for points it will not deliver to Ladybird. Reapply this after Qt creates the native surface, since Qt may restore the resizable style mask during Cocoa window setup. Handle the resize gesture in BrowserWindow instead, with thin side edges and a slightly larger corner target that avoids the rounded corner cutouts.
This commit is contained in:
parent
202159be18
commit
d707d112a2
4 changed files with 190 additions and 11 deletions
|
|
@ -64,6 +64,8 @@ static constexpr auto WINDOW_DRAG_REGION_PROPERTY = "LadybirdWindowDragRegion";
|
||||||
#if defined(AK_OS_MACOS)
|
#if defined(AK_OS_MACOS)
|
||||||
static constexpr qreal WINDOW_CORNER_RADIUS = 12.0;
|
static constexpr qreal WINDOW_CORNER_RADIUS = 12.0;
|
||||||
#endif
|
#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()
|
static bool should_use_screen_signal_for_dpi_changes()
|
||||||
{
|
{
|
||||||
|
|
@ -243,6 +245,7 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
|
||||||
setWindowIcon(app_icon());
|
setWindowIcon(app_icon());
|
||||||
qApp->installEventFilter(this);
|
qApp->installEventFilter(this);
|
||||||
update_window_corners();
|
update_window_corners();
|
||||||
|
update_appkit_window_resizability();
|
||||||
update_window_border();
|
update_window_border();
|
||||||
|
|
||||||
update_tabs_display();
|
update_tabs_display();
|
||||||
|
|
@ -992,6 +995,7 @@ void BrowserWindow::update_window_decoration_state()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_appkit_window_resizability();
|
||||||
update_menu_bar_visibility();
|
update_menu_bar_visibility();
|
||||||
update_window_border();
|
update_window_border();
|
||||||
}
|
}
|
||||||
|
|
@ -1160,6 +1164,7 @@ bool BrowserWindow::event(QEvent* event)
|
||||||
#if defined(AK_OS_MACOS)
|
#if defined(AK_OS_MACOS)
|
||||||
QTimer::singleShot(0, this, [this] {
|
QTimer::singleShot(0, this, [this] {
|
||||||
update_window_corners();
|
update_window_corners();
|
||||||
|
update_appkit_window_resizability();
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
} else if (platform_surface_event->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
|
} else if (platform_surface_event->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
|
||||||
|
|
@ -1169,10 +1174,17 @@ bool BrowserWindow::event(QEvent* event)
|
||||||
if (event->type() == QEvent::ScreenChangeInternal)
|
if (event->type() == QEvent::ScreenChangeInternal)
|
||||||
screen_changed(screen());
|
screen_changed(screen());
|
||||||
|
|
||||||
if (event->type() == QEvent::WindowActivate)
|
if (event->type() == QEvent::WindowActivate) {
|
||||||
Application::the().set_active_window(*this);
|
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();
|
clear_resize_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
return QMainWindow::event(event);
|
return QMainWindow::event(event);
|
||||||
}
|
}
|
||||||
|
|
@ -1185,6 +1197,18 @@ bool BrowserWindow::eventFilter(QObject* object, QEvent* event)
|
||||||
if (!uses_client_side_decorations())
|
if (!uses_client_side_decorations())
|
||||||
return QMainWindow::eventFilter(object, event);
|
return QMainWindow::eventFilter(object, event);
|
||||||
|
|
||||||
|
if (m_is_resizing_window) {
|
||||||
|
if (event->type() == QEvent::MouseMove) {
|
||||||
|
auto* mouse_event = static_cast<QMouseEvent*>(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<QAbstractButton*>(object) != nullptr;
|
auto const is_button = qobject_cast<QAbstractButton*>(object) != nullptr;
|
||||||
|
|
||||||
if (is_button && (event->type() == QEvent::Enter || event->type() == QEvent::MouseMove || event->type() == QEvent::Leave)) {
|
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);
|
return QMainWindow::eventFilter(object, event);
|
||||||
|
|
||||||
auto position = widget->mapTo(this, mouse_event->position().toPoint());
|
auto position = widget->mapTo(this, mouse_event->position().toPoint());
|
||||||
|
auto edges = resize_edges_for_position(position);
|
||||||
if (event->type() == QEvent::MouseButtonPress && !isMaximized() && !isFullScreen()) {
|
if (event->type() == QEvent::MouseButtonPress && !isMaximized() && !isFullScreen()) {
|
||||||
auto edges = resize_edges_for_position(position);
|
if (edges != Qt::Edges {}) {
|
||||||
auto* handle = windowHandle();
|
#if defined(AK_OS_MACOS)
|
||||||
if (edges != Qt::Edges {} && handle && handle->startSystemResize(edges))
|
if (start_window_resize(edges, mouse_event->globalPosition().toPoint()))
|
||||||
return true;
|
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()
|
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);
|
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<qreal>(position.x());
|
||||||
|
auto const y = static_cast<qreal>(position.y());
|
||||||
|
auto const right = static_cast<qreal>(width());
|
||||||
|
auto const bottom = static_cast<qreal>(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
|
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;
|
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;
|
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;
|
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;
|
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;
|
edges |= Qt::BottomEdge;
|
||||||
|
|
||||||
return edges;
|
return edges;
|
||||||
|
|
@ -1282,6 +1357,56 @@ Optional<Qt::CursorShape> BrowserWindow::resize_cursor_for_edges(Qt::Edges edges
|
||||||
return {};
|
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)
|
void BrowserWindow::update_resize_cursor(QPoint const& position)
|
||||||
{
|
{
|
||||||
if (!uses_client_side_decorations() || isMaximized() || isFullScreen() || !rect().contains(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<QWidget*>(object->parent())) {
|
||||||
|
if (qobject_cast<QAbstractButton*>(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()
|
void BrowserWindow::clear_resize_cursor()
|
||||||
{
|
{
|
||||||
if (!m_resize_cursor_active)
|
if (!m_resize_cursor_active)
|
||||||
|
|
@ -1373,6 +1517,13 @@ void BrowserWindow::update_window_corners()
|
||||||
#endif
|
#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
|
bool BrowserWindow::should_draw_window_border() const
|
||||||
{
|
{
|
||||||
#if defined(AK_OS_MACOS)
|
#if defined(AK_OS_MACOS)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
#include <QRect>
|
||||||
#include <QTabBar>
|
#include <QTabBar>
|
||||||
|
|
||||||
class QPropertyAnimation;
|
class QPropertyAnimation;
|
||||||
|
|
@ -167,11 +168,17 @@ private:
|
||||||
void uninitialize_tab(Tab*);
|
void uninitialize_tab(Tab*);
|
||||||
|
|
||||||
void set_current_tab(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;
|
Qt::Edges resize_edges_for_position(QPoint const&) const;
|
||||||
Optional<Qt::CursorShape> resize_cursor_for_edges(Qt::Edges) const;
|
Optional<Qt::CursorShape> 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 update_resize_cursor(QPoint const&);
|
||||||
|
void refresh_resize_cursor_at_current_position(bool force_reapply = false);
|
||||||
void clear_resize_cursor();
|
void clear_resize_cursor();
|
||||||
void update_window_corners();
|
void update_window_corners();
|
||||||
|
void update_appkit_window_resizability();
|
||||||
bool should_draw_window_border() const;
|
bool should_draw_window_border() const;
|
||||||
void update_window_border();
|
void update_window_border();
|
||||||
|
|
||||||
|
|
@ -232,6 +239,10 @@ private:
|
||||||
bool m_restore_to_maximized { false };
|
bool m_restore_to_maximized { false };
|
||||||
bool m_should_record_closed_window_on_close { true };
|
bool m_should_record_closed_window_on_close { true };
|
||||||
bool m_resize_cursor_active { false };
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ namespace Ladybird {
|
||||||
|
|
||||||
#if defined(AK_OS_MACOS)
|
#if defined(AK_OS_MACOS)
|
||||||
void set_rounded_window_corners(QWidget&, bool enabled, double radius, QColor const& background_color);
|
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_always_active_window_control_hover_tracking(QWidget&, void (*hover_changed)(QWidget*));
|
||||||
void install_appkit_event_capture();
|
void install_appkit_event_capture();
|
||||||
void make_appkit_window_first_responder(QWidget&);
|
void make_appkit_window_first_responder(QWidget&);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
content_view.layer.backgroundColor = cg_color_from_qcolor(background_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_appkit_window_resizable(QWidget& widget, bool enabled)
|
||||||
|
{
|
||||||
|
auto* view = reinterpret_cast<NSView*>(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*))
|
void install_always_active_window_control_hover_tracking(QWidget& widget, void (*hover_changed)(QWidget*))
|
||||||
{
|
{
|
||||||
static char tracker_key;
|
static char tracker_key;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue