LibWeb: Implement pinch-to-zoom support

Adds pinch event handling that adjusts the VisualViewport scale and
offset. VisualViewport's (offset, scale) is then used to construct a
transformation matrix which is applied before display list execution.
This commit is contained in:
Aliaksandr Kalenik 2025-10-09 17:30:12 +02:00 committed by Alexander Kalenik
parent b477c6bfc4
commit 9862d8b4a6
Notes: github-actions[bot] 2025-10-10 13:39:26 +00:00
14 changed files with 149 additions and 40 deletions

View file

@ -9,6 +9,7 @@
#include <LibUnicode/CharacterTypes.h>
#include <LibUnicode/Segmenter.h>
#include <LibWeb/CSS/VisualViewport.h>
#include <LibWeb/DOM/Text.h>
#include <LibWeb/Editing/Internal/Algorithms.h>
#include <LibWeb/HTML/CloseWatcherManager.h>
@ -383,17 +384,20 @@ GC::Ptr<Painting::PaintableBox const> EventHandler::paint_root() const
return m_navigable->active_document()->paintable_box();
}
EventResult EventHandler::handle_mousewheel(CSSPixelPoint viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers, int wheel_delta_x, int wheel_delta_y)
EventResult EventHandler::handle_mousewheel(CSSPixelPoint visual_viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers, int wheel_delta_x, int wheel_delta_y)
{
if (should_ignore_device_input_event())
return EventResult::Dropped;
if (!m_navigable->active_document())
auto document = m_navigable->active_document();
if (!document)
return EventResult::Dropped;
if (!m_navigable->active_document()->is_fully_active())
if (!document->is_fully_active())
return EventResult::Dropped;
m_navigable->active_document()->update_layout(DOM::UpdateLayoutReason::EventHandlerHandleMouseWheel);
auto viewport_position = document->visual_viewport()->map_to_layout_viewport(visual_viewport_position);
document->update_layout(DOM::UpdateLayoutReason::EventHandlerHandleMouseWheel);
if (!paint_root())
return EventResult::Dropped;
@ -448,16 +452,19 @@ EventResult EventHandler::handle_mousewheel(CSSPixelPoint viewport_position, CSS
return handled_event;
}
EventResult EventHandler::handle_mouseup(CSSPixelPoint viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
EventResult EventHandler::handle_mouseup(CSSPixelPoint visual_viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
{
if (should_ignore_device_input_event())
return EventResult::Dropped;
if (!m_navigable->active_document())
auto document = m_navigable->active_document();
if (!document)
return EventResult::Dropped;
if (!m_navigable->active_document()->is_fully_active())
if (!document->is_fully_active())
return EventResult::Dropped;
auto viewport_position = document->visual_viewport()->map_to_layout_viewport(visual_viewport_position);
m_navigable->active_document()->update_layout(DOM::UpdateLayoutReason::EventHandlerHandleMouseUp);
if (!paint_root())
@ -594,22 +601,24 @@ after_node_use:
return handled_event;
}
EventResult EventHandler::handle_mousedown(CSSPixelPoint viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
EventResult EventHandler::handle_mousedown(CSSPixelPoint visual_viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
{
if (should_ignore_device_input_event())
return EventResult::Dropped;
if (!m_navigable->active_document())
auto document = m_navigable->active_document();
if (!document)
return EventResult::Dropped;
if (!m_navigable->active_document()->is_fully_active())
if (!document->is_fully_active())
return EventResult::Dropped;
auto viewport_position = document->visual_viewport()->map_to_layout_viewport(visual_viewport_position);
m_navigable->active_document()->update_layout(DOM::UpdateLayoutReason::EventHandlerHandleMouseDown);
if (!paint_root())
return EventResult::Dropped;
GC::Ref<DOM::Document> document = *m_navigable->active_document();
GC::Ptr<DOM::Node> node;
ScopeGuard update_hovered_node_guard = [&node, &document] {
@ -723,7 +732,7 @@ EventResult EventHandler::handle_mousedown(CSSPixelPoint viewport_position, CSSP
return EventResult::Handled;
}
EventResult EventHandler::handle_mousemove(CSSPixelPoint viewport_position, CSSPixelPoint screen_position, u32 buttons, u32 modifiers)
EventResult EventHandler::handle_mousemove(CSSPixelPoint visual_viewport_position, CSSPixelPoint screen_position, u32 buttons, u32 modifiers)
{
if (should_ignore_device_input_event())
return EventResult::Dropped;
@ -733,13 +742,14 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint viewport_position, CSSP
if (!m_navigable->active_document()->is_fully_active())
return EventResult::Dropped;
auto& document = *m_navigable->active_document();
auto viewport_position = document.visual_viewport()->map_to_layout_viewport(visual_viewport_position);
m_navigable->active_document()->update_layout(DOM::UpdateLayoutReason::EventHandlerHandleMouseMove);
if (!paint_root())
return EventResult::Dropped;
auto& document = *m_navigable->active_document();
bool hovered_node_changed = false;
bool is_hovering_link = false;
Gfx::Cursor hovered_node_cursor = Gfx::StandardCursor::None;
@ -912,7 +922,7 @@ EventResult EventHandler::handle_mouseleave()
return EventResult::Handled;
}
EventResult EventHandler::handle_doubleclick(CSSPixelPoint viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
EventResult EventHandler::handle_doubleclick(CSSPixelPoint visual_viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers)
{
if (should_ignore_device_input_event())
return EventResult::Dropped;
@ -923,6 +933,7 @@ EventResult EventHandler::handle_doubleclick(CSSPixelPoint viewport_position, CS
return EventResult::Dropped;
auto& document = *m_navigable->active_document();
auto viewport_position = document.visual_viewport()->map_to_layout_viewport(visual_viewport_position);
document.update_layout(DOM::UpdateLayoutReason::EventHandlerHandleDoubleClick);
@ -1004,7 +1015,7 @@ EventResult EventHandler::handle_doubleclick(CSSPixelPoint viewport_position, CS
return EventResult::Handled;
}
EventResult EventHandler::handle_drag_and_drop_event(DragEvent::Type type, CSSPixelPoint viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers, Vector<HTML::SelectedFile> files)
EventResult EventHandler::handle_drag_and_drop_event(DragEvent::Type type, CSSPixelPoint visual_viewport_position, CSSPixelPoint screen_position, u32 button, u32 buttons, u32 modifiers, Vector<HTML::SelectedFile> files)
{
if (!m_navigable->active_document())
return EventResult::Dropped;
@ -1012,6 +1023,8 @@ EventResult EventHandler::handle_drag_and_drop_event(DragEvent::Type type, CSSPi
return EventResult::Dropped;
auto& document = *m_navigable->active_document();
auto viewport_position = document.visual_viewport()->map_to_layout_viewport(visual_viewport_position);
document.update_layout(DOM::UpdateLayoutReason::EventHandlerHandleDragAndDrop);
if (!paint_root())
@ -1050,6 +1063,19 @@ EventResult EventHandler::handle_drag_and_drop_event(DragEvent::Type type, CSSPi
VERIFY_NOT_REACHED();
}
EventResult EventHandler::handle_pinch_event(CSSPixelPoint point, double scale_delta)
{
auto document = m_navigable->active_document();
if (!document)
return EventResult::Dropped;
if (!document->is_fully_active())
return EventResult::Dropped;
auto visual_viewport = document->visual_viewport();
visual_viewport->zoom(point, scale_delta);
return EventResult::Handled;
}
EventResult EventHandler::focus_next_element()
{
if (!m_navigable->active_document())

View file

@ -37,6 +37,8 @@ public:
EventResult handle_drag_and_drop_event(DragEvent::Type, CSSPixelPoint, CSSPixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, Vector<HTML::SelectedFile> files);
EventResult handle_pinch_event(CSSPixelPoint, double scale_delta);
EventResult handle_keydown(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);
EventResult handle_keyup(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);

View file

@ -234,6 +234,11 @@ EventResult Page::handle_drag_and_drop_event(DragEvent::Type type, DevicePixelPo
return top_level_traversable()->event_handler().handle_drag_and_drop_event(type, device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers, move(files));
}
EventResult Page::handle_pinch_event(DevicePixelPoint position, double scale)
{
return top_level_traversable()->event_handler().handle_pinch_event(device_to_css_point(position), scale);
}
EventResult Page::handle_keydown(UIEvents::KeyCode key, unsigned modifiers, u32 code_point, bool repeat)
{
return focused_navigable().event_handler().handle_keydown(key, modifiers, code_point, repeat);

View file

@ -98,6 +98,7 @@ public:
EventResult handle_doubleclick(DevicePixelPoint, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers);
EventResult handle_drag_and_drop_event(DragEvent::Type, DevicePixelPoint, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, Vector<HTML::SelectedFile> files);
EventResult handle_pinch_event(DevicePixelPoint point, double scale);
EventResult handle_keydown(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);
EventResult handle_keyup(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);