diff --git a/UI/AppKit/Interface/LadybirdWebView.mm b/UI/AppKit/Interface/LadybirdWebView.mm index 834a847896..dcea137be3 100644 --- a/UI/AppKit/Interface/LadybirdWebView.mm +++ b/UI/AppKit/Interface/LadybirdWebView.mm @@ -51,6 +51,14 @@ static Optional display_id_for_screen(NSScreen* screen) return static_cast([screen_number unsignedLongLongValue]); } +static Web::DevicePixelPoint node_picker_position_for(Ladybird::WebViewBridge const& web_view_bridge, Web::DevicePixelPoint widget_position) +{ + return { + widget_position.x().value() * web_view_bridge.device_pixel_ratio(), + widget_position.y().value() * web_view_bridge.device_pixel_ratio(), + }; +} + @interface LadybirdWebViewContentLayer : CALayer @end @@ -931,6 +939,11 @@ static Optional display_id_for_screen(NSScreen* screen) if (!self.current_key_down_event) return; + if (m_web_view_bridge->is_node_picker_active()) { + self.current_key_down_event = nil; + return; + } + auto key_event = Ladybird::ns_event_to_key_event(Web::KeyEvent::Type::KeyDown, self.current_key_down_event); m_web_view_bridge->enqueue_input_event(move(key_event)); @@ -1128,6 +1141,11 @@ static Optional display_id_for_screen(NSScreen* screen) - (void)mouseExited:(NSEvent*)event { + if (m_web_view_bridge->is_node_picker_active()) { + m_web_view_bridge->clear_node_picker(); + return; + } + Web::MouseEvent mouse_event { Web::MouseEvent::Type::MouseLeave, {}, {}, Web::UIEvents::MouseButton::None, Web::UIEvents::MouseButton::None, Web::UIEvents::KeyModifier::Mod_None, 0, 0, 0, nullptr }; m_web_view_bridge->enqueue_input_event(move(mouse_event)); } @@ -1135,11 +1153,19 @@ static Optional display_id_for_screen(NSScreen* screen) - (void)mouseMoved:(NSEvent*)event { auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, Web::UIEvents::MouseButton::None); + if (m_web_view_bridge->is_node_picker_active()) { + m_web_view_bridge->node_picker_hover(node_picker_position_for(*m_web_view_bridge, mouse_event.position)); + return; + } + m_web_view_bridge->enqueue_input_event(move(mouse_event)); } - (void)scrollWheel:(NSEvent*)event { + if (m_web_view_bridge->is_node_picker_active()) + return; + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseWheel, event, self, Web::UIEvents::MouseButton::Middle); m_web_view_bridge->enqueue_input_event(move(mouse_event)); } @@ -1149,17 +1175,31 @@ static Optional display_id_for_screen(NSScreen* screen) [[self window] makeFirstResponder:self]; auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseDown, event, self, Web::UIEvents::MouseButton::Primary); + if (m_web_view_bridge->is_node_picker_active()) { + if ((event.modifierFlags & NSEventModifierFlagCommand) != 0) + m_web_view_bridge->node_picker_preview(node_picker_position_for(*m_web_view_bridge, mouse_event.position)); + else + m_web_view_bridge->node_picker_pick(node_picker_position_for(*m_web_view_bridge, mouse_event.position)); + return; + } + m_web_view_bridge->enqueue_input_event(move(mouse_event)); } - (void)mouseUp:(NSEvent*)event { + if (m_web_view_bridge->is_node_picker_active()) + return; + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseUp, event, self, Web::UIEvents::MouseButton::Primary); m_web_view_bridge->enqueue_input_event(move(mouse_event)); } - (void)mouseDragged:(NSEvent*)event { + if (m_web_view_bridge->is_node_picker_active()) + return; + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, Web::UIEvents::MouseButton::Primary); m_web_view_bridge->enqueue_input_event(move(mouse_event)); } @@ -1168,18 +1208,27 @@ static Optional display_id_for_screen(NSScreen* screen) { [[self window] makeFirstResponder:self]; + if (m_web_view_bridge->is_node_picker_active()) + return; + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseDown, event, self, Web::UIEvents::MouseButton::Secondary); m_web_view_bridge->enqueue_input_event(move(mouse_event)); } - (void)rightMouseUp:(NSEvent*)event { + if (m_web_view_bridge->is_node_picker_active()) + return; + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseUp, event, self, Web::UIEvents::MouseButton::Secondary); m_web_view_bridge->enqueue_input_event(move(mouse_event)); } - (void)rightMouseDragged:(NSEvent*)event { + if (m_web_view_bridge->is_node_picker_active()) + return; + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, Web::UIEvents::MouseButton::Secondary); m_web_view_bridge->enqueue_input_event(move(mouse_event)); } @@ -1191,6 +1240,9 @@ static Optional display_id_for_screen(NSScreen* screen) [[self window] makeFirstResponder:self]; + if (m_web_view_bridge->is_node_picker_active()) + return; + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseDown, event, self, Web::UIEvents::MouseButton::Middle); m_web_view_bridge->enqueue_input_event(move(mouse_event)); } @@ -1200,6 +1252,9 @@ static Optional display_id_for_screen(NSScreen* screen) if (event.buttonNumber != 2) return; + if (m_web_view_bridge->is_node_picker_active()) + return; + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseUp, event, self, Web::UIEvents::MouseButton::Middle); m_web_view_bridge->enqueue_input_event(move(mouse_event)); } @@ -1209,6 +1264,9 @@ static Optional display_id_for_screen(NSScreen* screen) if (event.buttonNumber != 2) return; + if (m_web_view_bridge->is_node_picker_active()) + return; + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, Web::UIEvents::MouseButton::Middle); m_web_view_bridge->enqueue_input_event(move(mouse_event)); } @@ -1235,6 +1293,13 @@ static Optional display_id_for_screen(NSScreen* screen) return; } + if (m_web_view_bridge->is_node_picker_active()) { + auto key_event = Ladybird::ns_event_to_key_event(Web::KeyEvent::Type::KeyDown, event); + if (key_event.key == Web::UIEvents::KeyCode::Key_Escape) + m_web_view_bridge->node_picker_cancel(); + return; + } + self.current_key_down_event = event; [self interpretKeyEvents:@[ event ]]; } @@ -1245,6 +1310,9 @@ static Optional display_id_for_screen(NSScreen* screen) return; } + if (m_web_view_bridge->is_node_picker_active()) + return; + auto key_event = Ladybird::ns_event_to_key_event(Web::KeyEvent::Type::KeyUp, event); m_web_view_bridge->enqueue_input_event(move(key_event)); } @@ -1255,6 +1323,11 @@ static Optional display_id_for_screen(NSScreen* screen) return; } + if (m_web_view_bridge->is_node_picker_active()) { + m_modifier_flags = event.modifierFlags; + return; + } + auto enqueue_event_if_needed = [&](auto flag) { auto is_flag_set = [&](auto flags) { return (flags & flag) != 0; }; Web::KeyEvent::Type type; diff --git a/UI/Gtk/Widgets/LadybirdWebView.cpp b/UI/Gtk/Widgets/LadybirdWebView.cpp index 3a2db9dcf2..2330ae3756 100644 --- a/UI/Gtk/Widgets/LadybirdWebView.cpp +++ b/UI/Gtk/Widgets/LadybirdWebView.cpp @@ -87,6 +87,12 @@ static gboolean on_key_pressed(GtkEventControllerKey*, guint keyval, guint, GdkM if (!self->impl) return GDK_EVENT_PROPAGATE; + if (self->impl->is_node_picker_active()) { + if (keyval == GDK_KEY_Escape) + self->impl->node_picker_cancel(); + return GDK_EVENT_STOP; + } + self->impl->enqueue_native_event(Web::KeyEvent::Type::KeyDown, keyval, state); return GDK_EVENT_STOP; } @@ -97,6 +103,9 @@ static void on_key_released(GtkEventControllerKey*, guint keyval, guint, GdkModi if (!self->impl) return; + if (self->impl->is_node_picker_active()) + return; + self->impl->enqueue_native_event(Web::KeyEvent::Type::KeyUp, keyval, state); } @@ -110,6 +119,18 @@ static void on_mouse_pressed(GtkGestureClick* gesture, gint n_press, gdouble x, auto button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture)); auto state = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(gesture)); + if (self->impl->is_node_picker_active()) { + if (button == GDK_BUTTON_PRIMARY) { + auto position = Web::DevicePixelPoint { static_cast(x * self->impl->device_pixel_ratio()), static_cast(y * self->impl->device_pixel_ratio()) }; + if (state & GDK_CONTROL_MASK) + self->impl->node_picker_preview(position); + else + self->impl->node_picker_pick(position); + } + gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_CLAIMED); + return; + } + self->impl->enqueue_native_event(Web::MouseEvent::Type::MouseDown, x, y, button, state, n_press); } @@ -119,6 +140,11 @@ static void on_mouse_released(GtkGestureClick* gesture, gint n_press, gdouble x, if (!self->impl) return; + if (self->impl->is_node_picker_active()) { + gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_CLAIMED); + return; + } + auto button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture)); auto state = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(gesture)); self->impl->enqueue_native_event(Web::MouseEvent::Type::MouseUp, x, y, button, state, n_press); @@ -132,6 +158,12 @@ static void on_mouse_motion(GtkEventControllerMotion* controller, gdouble x, gdo if (!self->impl) return; + if (self->impl->is_node_picker_active()) { + auto position = Web::DevicePixelPoint { static_cast(x * self->impl->device_pixel_ratio()), static_cast(y * self->impl->device_pixel_ratio()) }; + self->impl->node_picker_hover(position); + return; + } + auto state = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(controller)); self->impl->enqueue_native_event(Web::MouseEvent::Type::MouseMove, x, y, 0, state, 0); } @@ -142,6 +174,11 @@ static void on_mouse_leave(GtkEventControllerMotion*, gpointer user_data) if (!self->impl) return; + if (self->impl->is_node_picker_active()) { + self->impl->clear_node_picker(); + return; + } + self->impl->enqueue_native_event(Web::MouseEvent::Type::MouseLeave, 0, 0, 0, static_cast(0), 0); } @@ -151,6 +188,9 @@ static gboolean on_scroll(GtkEventControllerScroll* controller, gdouble dx, gdou if (!self->impl) return GDK_EVENT_PROPAGATE; + if (self->impl->is_node_picker_active()) + return GDK_EVENT_STOP; + auto state = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(controller)); // Ctrl+scroll = zoom diff --git a/UI/Qt/WebContentView.cpp b/UI/Qt/WebContentView.cpp index 9dc986abad..57b2e228d6 100644 --- a/UI/Qt/WebContentView.cpp +++ b/UI/Qt/WebContentView.cpp @@ -420,16 +420,33 @@ static bool is_browser_reserved_shortcut(QKeyEvent const& event) void WebContentView::keyPressEvent(QKeyEvent* event) { + if (is_node_picker_active()) { + if (event->key() == Qt::Key_Escape) + node_picker_cancel(); + event->accept(); + return; + } + enqueue_native_event(Web::KeyEvent::Type::KeyDown, *event); } void WebContentView::keyReleaseEvent(QKeyEvent* event) { + if (is_node_picker_active()) { + event->accept(); + return; + } + enqueue_native_event(Web::KeyEvent::Type::KeyUp, *event); } void WebContentView::inputMethodEvent(QInputMethodEvent* event) { + if (is_node_picker_active()) { + event->accept(); + return; + } + if (!event->commitString().isEmpty()) { QKeyEvent keyEvent(QEvent::KeyPress, 0, Qt::NoModifier, event->commitString()); keyPressEvent(&keyEvent); @@ -444,6 +461,12 @@ QVariant WebContentView::inputMethodQuery(Qt::InputMethodQuery) const void WebContentView::leaveEvent(QEvent* event) { + if (is_node_picker_active()) { + clear_node_picker(); + QWidget::leaveEvent(event); + return; + } + static QMouseEvent mouse_event { QEvent::Type::Leave, {}, {}, Qt::MouseButton::NoButton, Qt::MouseButton::NoButton, Qt::KeyboardModifier::NoModifier }; enqueue_native_event(Web::MouseEvent::Type::MouseLeave, mouse_event); @@ -452,6 +475,12 @@ void WebContentView::leaveEvent(QEvent* event) void WebContentView::mouseMoveEvent(QMouseEvent* event) { + if (is_node_picker_active()) { + node_picker_hover(node_picker_position_for(*event)); + event->accept(); + return; + } + if (!m_tooltip_override) { if (QToolTip::isVisible()) QToolTip::hideText(); @@ -464,6 +493,18 @@ void WebContentView::mouseMoveEvent(QMouseEvent* event) void WebContentView::mousePressEvent(QMouseEvent* event) { + if (is_node_picker_active()) { + if (event->button() == Qt::MouseButton::LeftButton) { + auto position = node_picker_position_for(*event); + if (event->modifiers().testFlag(Qt::ControlModifier)) + node_picker_preview(position); + else + node_picker_pick(position); + } + event->accept(); + return; + } + auto elapsed = event->timestamp() - m_last_click_timestamp; auto distance = (event->position() - m_last_click_position).manhattanLength(); @@ -482,6 +523,11 @@ void WebContentView::mousePressEvent(QMouseEvent* event) void WebContentView::mouseReleaseEvent(QMouseEvent* event) { + if (is_node_picker_active()) { + event->accept(); + return; + } + enqueue_native_event(Web::MouseEvent::Type::MouseUp, *event); if (event->button() == Qt::MouseButton::BackButton) @@ -492,6 +538,11 @@ void WebContentView::mouseReleaseEvent(QMouseEvent* event) void WebContentView::wheelEvent(QWheelEvent* event) { + if (is_node_picker_active()) { + event->accept(); + return; + } + if (event->modifiers().testFlag(Qt::ControlModifier)) { event->ignore(); return; @@ -509,6 +560,11 @@ void WebContentView::mouseDoubleClickEvent(QMouseEvent* event) void WebContentView::dragEnterEvent(QDragEnterEvent* event) { + if (is_node_picker_active()) { + event->ignore(); + return; + } + if (!event->mimeData()->hasUrls()) return; @@ -518,12 +574,20 @@ void WebContentView::dragEnterEvent(QDragEnterEvent* event) void WebContentView::dragMoveEvent(QDragMoveEvent* event) { + if (is_node_picker_active()) { + event->ignore(); + return; + } + enqueue_native_event(Web::DragEvent::Type::DragMove, *event); event->acceptProposedAction(); } void WebContentView::dragLeaveEvent(QDragLeaveEvent*) { + if (is_node_picker_active()) + return; + // QDragLeaveEvent does not contain any mouse position or button information. Web::DragEvent event {}; event.type = Web::DragEvent::Type::DragEnd; @@ -533,6 +597,11 @@ void WebContentView::dragLeaveEvent(QDragLeaveEvent*) void WebContentView::dropEvent(QDropEvent* event) { + if (is_node_picker_active()) { + event->ignore(); + return; + } + enqueue_native_event(Web::DragEvent::Type::Drop, *event); event->acceptProposedAction(); } @@ -813,6 +882,11 @@ void WebContentView::update_cursor(Gfx::Cursor cursor) }); } +Web::DevicePixelPoint WebContentView::node_picker_position_for(QSinglePointEvent const& event) const +{ + return { event.position().x() * m_device_pixel_ratio, event.position().y() * m_device_pixel_ratio }; +} + Web::DevicePixelSize WebContentView::viewport_size() const { return m_viewport_size.to_type(); diff --git a/UI/Qt/WebContentView.h b/UI/Qt/WebContentView.h index d4f302e786..271fc3ecde 100644 --- a/UI/Qt/WebContentView.h +++ b/UI/Qt/WebContentView.h @@ -97,6 +97,8 @@ private: void update_cursor(Gfx::Cursor cursor); void update_compositor_display_metadata(); + Web::DevicePixelPoint node_picker_position_for(QSinglePointEvent const&) const; + void enqueue_native_event(Web::MouseEvent::Type, QSinglePointEvent const& event); void enqueue_native_event(Web::DragEvent::Type, QDropEvent const& event);