UI: Route node picker input through WebView
Picker input starts in each platform UI, while protocol state lives in LibWebView. Route movement, primary clicks, Escape, and leave events through the WebView picker API so each frontend drives the same hit-test and picker-event path. The selected click is consumed because picker mode uses it to choose an element for DevTools rather than activate page content. AppKit routes normal mouse input through WebViewBridge, which converts view points to device pixels before forwarding to LibWeb. Picker input bypasses that enqueue path, so apply the same scaling before requesting a picker hit test.
This commit is contained in:
parent
3a332b23e5
commit
e5e3099ce7
4 changed files with 189 additions and 0 deletions
|
|
@ -51,6 +51,14 @@ static Optional<u64> display_id_for_screen(NSScreen* screen)
|
|||
return static_cast<u64>([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<u64> 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<u64> 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<u64> 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<u64> 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<u64> 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<u64> 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<u64> 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<u64> 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<u64> 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<u64> 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<u64> 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;
|
||||
|
|
|
|||
|
|
@ -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<int>(x * self->impl->device_pixel_ratio()), static_cast<int>(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<int>(x * self->impl->device_pixel_ratio()), static_cast<int>(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<GdkModifierType>(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
|
||||
|
|
|
|||
|
|
@ -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<Web::DevicePixels>();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue