diff --git a/Libraries/LibWeb/CSS/VisualViewport.cpp b/Libraries/LibWeb/CSS/VisualViewport.cpp index 01920b29253..8d7b673c617 100644 --- a/Libraries/LibWeb/CSS/VisualViewport.cpp +++ b/Libraries/LibWeb/CSS/VisualViewport.cpp @@ -145,4 +145,41 @@ WebIDL::CallbackType* VisualViewport::onscrollend() return event_handler_attribute(HTML::EventNames::scrollend); } +Gfx::AffineTransform VisualViewport::transform() const +{ + Gfx::AffineTransform transform; + auto offset = m_offset.to_type() * m_scale; + transform.translate(-offset.x(), -offset.y()); + transform.scale({ m_scale, m_scale }); + return transform; +} + +void VisualViewport::zoom(CSSPixelPoint position, double scale_delta) +{ + static constexpr double MIN_ALLOWED_SCALE = 1.0; + static constexpr double MAX_ALLOWED_SCALE = 5.0; + double new_scale = clamp(m_scale * (1 + scale_delta), MIN_ALLOWED_SCALE, MAX_ALLOWED_SCALE); + double applied_delta = new_scale / m_scale; + + // For pinch zoom we want focal_point to stay put on screen: + // scale_new * (focal_point - offset_new) = scale_old * (focal_point - offset_old) + auto new_offset = m_offset.to_type() * m_scale * applied_delta; + new_offset += position.to_type().to_type() * (applied_delta - 1.0f); + + auto viewport_float_size = m_document->navigable()->viewport_rect().size().to_type(); + auto max_x_offset = max(0.0, viewport_float_size.width() * (new_scale - 1.0f)); + auto max_y_offset = max(0.0, viewport_float_size.height() * (new_scale - 1.0f)); + new_offset = { clamp(new_offset.x(), 0.0f, max_x_offset), clamp(new_offset.y(), 0.0f, max_y_offset) }; + + m_scale = new_scale; + m_offset = (new_offset / m_scale).to_type(); + m_document->set_needs_display(InvalidateDisplayList::No); +} + +CSSPixelPoint VisualViewport::map_to_layout_viewport(CSSPixelPoint position) const +{ + auto inverse = transform().inverse().value_or({}); + return inverse.map(position.to_type()).to_type(); +} + } diff --git a/Libraries/LibWeb/CSS/VisualViewport.h b/Libraries/LibWeb/CSS/VisualViewport.h index 091e6fb854d..7ee9c4b1995 100644 --- a/Libraries/LibWeb/CSS/VisualViewport.h +++ b/Libraries/LibWeb/CSS/VisualViewport.h @@ -44,6 +44,10 @@ public: void scroll_by(CSSPixelPoint delta) { m_offset += delta; } + [[nodiscard]] Gfx::AffineTransform transform() const; + void zoom(CSSPixelPoint position, double scale_delta); + CSSPixelPoint map_to_layout_viewport(CSSPixelPoint) const; + private: explicit VisualViewport(DOM::Document&); diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index c98b612461b..35bba95920d 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -6478,7 +6478,17 @@ RefPtr Document::cached_display_list() const RefPtr Document::record_display_list(HTML::PaintConfig config) { + auto update_visual_viewport_transform = [&](Painting::DisplayList& display_list) { + auto transform = visual_viewport()->transform(); + auto matrix = transform.to_matrix(); + matrix[0, 3] *= display_list.device_pixels_per_css_pixel(); + matrix[1, 3] *= display_list.device_pixels_per_css_pixel(); + matrix[2, 3] *= display_list.device_pixels_per_css_pixel(); + display_list.set_visual_viewport_transform(matrix); + }; + if (m_cached_display_list && m_cached_display_list_paint_config == config) { + update_visual_viewport_transform(*m_cached_display_list); return m_cached_display_list; } @@ -6542,6 +6552,7 @@ RefPtr Document::record_display_list(HTML::PaintConfig co m_cached_display_list = display_list; m_cached_display_list_paint_config = config; + update_visual_viewport_transform(*m_cached_display_list); return display_list; } diff --git a/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp b/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp index 88e40328748..d4a1fa691c4 100644 --- a/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp +++ b/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp @@ -291,8 +291,8 @@ void EventLoop::process_input_events() const [&](Web::DragEvent& drag_event) { return page.handle_drag_and_drop_event(drag_event.type, drag_event.position, drag_event.screen_position, drag_event.button, drag_event.buttons, drag_event.modifiers, move(drag_event.files)); }, - [&](Web::PinchEvent&) { - return EventResult::Dropped; + [&](Web::PinchEvent& pinch_event) { + return page.handle_pinch_event(pinch_event.position, pinch_event.scale_delta); }); for (size_t i = 0; i < event.coalesced_event_count; ++i) diff --git a/Libraries/LibWeb/Page/EventHandler.cpp b/Libraries/LibWeb/Page/EventHandler.cpp index 17eff89bade..eaa3b9fb973 100644 --- a/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Libraries/LibWeb/Page/EventHandler.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -383,17 +384,20 @@ GC::Ptr 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 document = *m_navigable->active_document(); GC::Ptr 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 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 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()) diff --git a/Libraries/LibWeb/Page/EventHandler.h b/Libraries/LibWeb/Page/EventHandler.h index 0fe3d9bf83c..6a47bc175ce 100644 --- a/Libraries/LibWeb/Page/EventHandler.h +++ b/Libraries/LibWeb/Page/EventHandler.h @@ -37,6 +37,8 @@ public: EventResult handle_drag_and_drop_event(DragEvent::Type, CSSPixelPoint, CSSPixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, Vector 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); diff --git a/Libraries/LibWeb/Page/Page.cpp b/Libraries/LibWeb/Page/Page.cpp index 3e744bb003b..535048a1631 100644 --- a/Libraries/LibWeb/Page/Page.cpp +++ b/Libraries/LibWeb/Page/Page.cpp @@ -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); diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index 5177f4872c4..bd66fb732c7 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -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 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); diff --git a/Libraries/LibWeb/Painting/DisplayList.h b/Libraries/LibWeb/Painting/DisplayList.h index 5834d5ba785..5b84576912b 100644 --- a/Libraries/LibWeb/Painting/DisplayList.h +++ b/Libraries/LibWeb/Painting/DisplayList.h @@ -109,6 +109,9 @@ public: } } + static constexpr size_t VISUAL_VIEWPORT_TRANSFORM_INDEX = 1; + void set_visual_viewport_transform(Gfx::FloatMatrix4x4 t) { m_commands[VISUAL_VIEWPORT_TRANSFORM_INDEX].command.get().matrix = t; } + private: DisplayList(double device_pixels_per_css_pixel) : m_device_pixels_per_css_pixel(device_pixels_per_css_pixel) @@ -117,6 +120,7 @@ private: AK::SegmentedVector m_commands; double m_device_pixels_per_css_pixel; + Optional m_visual_viewport_transform; }; } diff --git a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp index a9d168f5bb3..ed012c7649c 100644 --- a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp +++ b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp @@ -24,9 +24,16 @@ StackingContextTransform::StackingContextTransform(Gfx::FloatPoint origin, Gfx:: DisplayListRecorder::DisplayListRecorder(DisplayList& command_list) : m_display_list(command_list) { + save(); + // Reserve for visual viewport transform + VERIFY(m_display_list.commands().size() == DisplayList::VISUAL_VIEWPORT_TRANSFORM_INDEX); + apply_transform({}, Gfx::FloatMatrix4x4::identity()); } -DisplayListRecorder::~DisplayListRecorder() = default; +DisplayListRecorder::~DisplayListRecorder() +{ + restore(); +} template consteval static int command_nesting_level_change(T const& command) diff --git a/Tests/LibWeb/Text/expected/css/text-underline-position.txt b/Tests/LibWeb/Text/expected/css/text-underline-position.txt index f7162e58943..2ff4b28b0c6 100644 --- a/Tests/LibWeb/Text/expected/css/text-underline-position.txt +++ b/Tests/LibWeb/Text/expected/css/text-underline-position.txt @@ -1,8 +1,11 @@ -SaveLayer - DrawGlyphRun rect=[35,8 8x18] translation=[35.15625,21.796875] color=rgb(0, 0, 0) scale=1 - DrawGlyphRun rect=[8,8 28x18] translation=[8,21.796875] color=rgb(0, 0, 0) scale=1 - DrawLine from=[8,24] to=[35,24] color=rgb(0, 0, 0) thickness=2 - DrawGlyphRun rect=[43,8 28x18] translation=[43.15625,21.796875] color=rgb(0, 0, 0) scale=1 - DrawLine from=[43,27] to=[71,27] color=rgb(0, 0, 0) thickness=2 +Save + ApplyTransform matrix=[1 0 0 1 0 0] + SaveLayer + DrawGlyphRun rect=[35,8 8x18] translation=[35.15625,21.796875] color=rgb(0, 0, 0) scale=1 + DrawGlyphRun rect=[8,8 28x18] translation=[8,21.796875] color=rgb(0, 0, 0) scale=1 + DrawLine from=[8,24] to=[35,24] color=rgb(0, 0, 0) thickness=2 + DrawGlyphRun rect=[43,8 28x18] translation=[43.15625,21.796875] color=rgb(0, 0, 0) scale=1 + DrawLine from=[43,27] to=[71,27] color=rgb(0, 0, 0) thickness=2 + Restore Restore diff --git a/Tests/LibWeb/Text/expected/display_list/button-with-text-decoration.txt b/Tests/LibWeb/Text/expected/display_list/button-with-text-decoration.txt index 26899c2ac2e..7209db0013c 100644 --- a/Tests/LibWeb/Text/expected/display_list/button-with-text-decoration.txt +++ b/Tests/LibWeb/Text/expected/display_list/button-with-text-decoration.txt @@ -1,7 +1,10 @@ -SaveLayer - FillRect rect=[8,8 106x22] color=rgb(212, 208, 200) - FillPath - DrawGlyphRun rect=[13,10 96x18] translation=[13,23.796875] color=rgb(0, 0, 0) scale=1 - DrawLine from=[13,26] to=[109,26] color=rgb(0, 0, 0) thickness=2 +Save + ApplyTransform matrix=[1 0 0 1 0 0] + SaveLayer + FillRect rect=[8,8 106x22] color=rgb(212, 208, 200) + FillPath + DrawGlyphRun rect=[13,10 96x18] translation=[13,23.796875] color=rgb(0, 0, 0) scale=1 + DrawLine from=[13,26] to=[109,26] color=rgb(0, 0, 0) thickness=2 + Restore Restore diff --git a/Tests/LibWeb/Text/expected/display_list/grid-gap-rounding.txt b/Tests/LibWeb/Text/expected/display_list/grid-gap-rounding.txt index 6b7d7b087b1..f452baa2d73 100644 --- a/Tests/LibWeb/Text/expected/display_list/grid-gap-rounding.txt +++ b/Tests/LibWeb/Text/expected/display_list/grid-gap-rounding.txt @@ -1,8 +1,11 @@ -SaveLayer - FillRect rect=[8,8 29x30] color=rgb(0, 0, 0) - FillRect rect=[38,8 28x30] color=rgb(0, 0, 0) - FillRect rect=[67,8 29x30] color=rgb(0, 0, 0) - FillRect rect=[97,8 28x30] color=rgb(0, 0, 0) - FillRect rect=[126,8 29x30] color=rgb(0, 0, 0) +Save + ApplyTransform matrix=[1 0 0 1 0 0] + SaveLayer + FillRect rect=[8,8 29x30] color=rgb(0, 0, 0) + FillRect rect=[38,8 28x30] color=rgb(0, 0, 0) + FillRect rect=[67,8 29x30] color=rgb(0, 0, 0) + FillRect rect=[97,8 28x30] color=rgb(0, 0, 0) + FillRect rect=[126,8 29x30] color=rgb(0, 0, 0) + Restore Restore diff --git a/Tests/LibWeb/Text/expected/display_list/simple-overflow-hidden.txt b/Tests/LibWeb/Text/expected/display_list/simple-overflow-hidden.txt index 9b23071c078..510915027eb 100644 --- a/Tests/LibWeb/Text/expected/display_list/simple-overflow-hidden.txt +++ b/Tests/LibWeb/Text/expected/display_list/simple-overflow-hidden.txt @@ -1,6 +1,9 @@ -SaveLayer - FillPath - FillRect rect=[10,10 300x150] color=rgb(240, 128, 128) - DrawGlyphRun rect=[10,10 38x18] translation=[10,23.796875] color=rgb(0, 0, 0) scale=1 +Save + ApplyTransform matrix=[1 0 0 1 0 0] + SaveLayer + FillPath + FillRect rect=[10,10 300x150] color=rgb(240, 128, 128) + DrawGlyphRun rect=[10,10 38x18] translation=[10,23.796875] color=rgb(0, 0, 0) scale=1 + Restore Restore