Browse Source

LibWeb: Update hit_test for CSS Transforms

This now also takes a FloatPoint instead of an IntPoint to avoid
excessive rounding when multiple transforms apply on top of each other.
Simon Wanner 3 years ago
parent
commit
48efdaa8c4

+ 7 - 7
Userland/Libraries/LibWeb/Page/EventHandler.cpp

@@ -138,7 +138,7 @@ bool EventHandler::handle_mousewheel(const Gfx::IntPoint& position, unsigned int
 
     // FIXME: Support wheel events in nested browsing contexts.
 
-    auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
+    auto result = paint_root()->hit_test(position.to_type<float>(), Painting::HitTestType::Exact);
     if (result.paintable && result.paintable->handle_mousewheel({}, position, buttons, modifiers, wheel_delta_x, wheel_delta_y))
         return true;
 
@@ -164,7 +164,7 @@ bool EventHandler::handle_mouseup(const Gfx::IntPoint& position, unsigned button
     if (m_mouse_event_tracking_layout_node) {
         paintable = m_mouse_event_tracking_layout_node->paintable();
     } else {
-        auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
+        auto result = paint_root()->hit_test(position.to_type<float>(), Painting::HitTestType::Exact);
         paintable = result.paintable;
     }
 
@@ -175,7 +175,7 @@ bool EventHandler::handle_mouseup(const Gfx::IntPoint& position, unsigned button
         // Things may have changed as a consequence of Layout::Node::handle_mouseup(). Hit test again.
         if (!paint_root())
             return true;
-        auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
+        auto result = paint_root()->hit_test(position.to_type<float>(), Painting::HitTestType::Exact);
         paintable = result.paintable;
     }
 
@@ -222,7 +222,7 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt
         if (m_mouse_event_tracking_layout_node) {
             paintable = m_mouse_event_tracking_layout_node->paintable();
         } else {
-            auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
+            auto result = paint_root()->hit_test(position.to_type<float>(), Painting::HitTestType::Exact);
             if (!result.paintable)
                 return false;
             paintable = result.paintable;
@@ -300,7 +300,7 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt
         }
     } else {
         if (button == GUI::MouseButton::Primary) {
-            auto result = paint_root()->hit_test(position, Painting::HitTestType::TextCursor);
+            auto result = paint_root()->hit_test(position.to_type<float>(), Painting::HitTestType::TextCursor);
             if (result.paintable && result.paintable->layout_node().dom_node()) {
 
                 // See if we want to focus something.
@@ -348,7 +348,7 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt
     if (m_mouse_event_tracking_layout_node) {
         paintable = m_mouse_event_tracking_layout_node->paintable();
     } else {
-        auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact);
+        auto result = paint_root()->hit_test(position.to_type<float>(), Painting::HitTestType::Exact);
         paintable = result.paintable;
         start_index = result.index_in_node;
     }
@@ -408,7 +408,7 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt
                 return true;
         }
         if (m_in_mouse_selection) {
-            auto hit = paint_root()->hit_test(position, Painting::HitTestType::TextCursor);
+            auto hit = paint_root()->hit_test(position.to_type<float>(), Painting::HitTestType::TextCursor);
             if (start_index.has_value() && hit.paintable && hit.paintable->layout_node().dom_node()) {
                 m_browsing_context.set_cursor_position(DOM::Position(*hit.paintable->layout_node().dom_node(), *start_index));
                 layout_root()->set_selection_end({ hit.paintable->layout_node(), hit.index_in_node });

+ 1 - 1
Userland/Libraries/LibWeb/Painting/Paintable.cpp

@@ -41,7 +41,7 @@ bool Paintable::handle_mousewheel(Badge<EventHandler>, Gfx::IntPoint const&, uns
     return false;
 }
 
-HitTestResult Paintable::hit_test(Gfx::IntPoint const&, HitTestType) const
+HitTestResult Paintable::hit_test(Gfx::FloatPoint const&, HitTestType) const
 {
     VERIFY_NOT_REACHED();
 }

+ 1 - 1
Userland/Libraries/LibWeb/Painting/Paintable.h

@@ -50,7 +50,7 @@ public:
     virtual void before_children_paint(PaintContext&, PaintPhase) const { }
     virtual void after_children_paint(PaintContext&, PaintPhase) const { }
 
-    virtual HitTestResult hit_test(Gfx::IntPoint const&, HitTestType) const;
+    virtual HitTestResult hit_test(Gfx::FloatPoint const&, HitTestType) const;
 
     virtual bool wants_mouse_events() const { return false; }
 

+ 3 - 3
Userland/Libraries/LibWeb/Painting/PaintableBox.cpp

@@ -526,7 +526,7 @@ void PaintableBox::for_each_child_in_paint_order(Callback callback) const
     });
 }
 
-HitTestResult PaintableBox::hit_test(Gfx::IntPoint const& position, HitTestType type) const
+HitTestResult PaintableBox::hit_test(Gfx::FloatPoint const& position, HitTestType type) const
 {
     if (layout_box().is_initial_containing_block_box())
         return stacking_context()->hit_test(position, type);
@@ -542,7 +542,7 @@ HitTestResult PaintableBox::hit_test(Gfx::IntPoint const& position, HitTestType
     return result;
 }
 
-HitTestResult PaintableWithLines::hit_test(const Gfx::IntPoint& position, HitTestType type) const
+HitTestResult PaintableWithLines::hit_test(const Gfx::FloatPoint& position, HitTestType type) const
 {
     if (!layout_box().children_are_inline())
         return PaintableBox::hit_test(position, type);
@@ -552,7 +552,7 @@ HitTestResult PaintableWithLines::hit_test(const Gfx::IntPoint& position, HitTes
         for (auto& fragment : line_box.fragments()) {
             if (is<Layout::Box>(fragment.layout_node()) && static_cast<Layout::Box const&>(fragment.layout_node()).paint_box()->stacking_context())
                 continue;
-            if (enclosing_int_rect(fragment.absolute_rect()).contains(position)) {
+            if (fragment.absolute_rect().contains(position)) {
                 if (is<Layout::BlockContainer>(fragment.layout_node()) && fragment.layout_node().paintable())
                     return fragment.layout_node().paintable()->hit_test(position, type);
                 return { fragment.layout_node().paintable(), fragment.text_index_at(position.x()) };

+ 2 - 2
Userland/Libraries/LibWeb/Painting/PaintableBox.h

@@ -115,7 +115,7 @@ public:
     virtual void before_children_paint(PaintContext&, PaintPhase) const override;
     virtual void after_children_paint(PaintContext&, PaintPhase) const override;
 
-    virtual HitTestResult hit_test(Gfx::IntPoint const&, HitTestType) const override;
+    virtual HitTestResult hit_test(Gfx::FloatPoint const&, HitTestType) const override;
 
 protected:
     explicit PaintableBox(Layout::Box const&);
@@ -164,7 +164,7 @@ public:
     virtual bool wants_mouse_events() const override { return false; }
     virtual bool handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y) override;
 
-    virtual HitTestResult hit_test(Gfx::IntPoint const&, HitTestType) const override;
+    virtual HitTestResult hit_test(Gfx::FloatPoint const&, HitTestType) const override;
 
 protected:
     PaintableWithLines(Layout::BlockContainer const&);

+ 13 - 8
Userland/Libraries/LibWeb/Painting/StackingContext.cpp

@@ -272,8 +272,13 @@ void StackingContext::paint(PaintContext& context) const
     }
 }
 
-HitTestResult StackingContext::hit_test(Gfx::IntPoint const& position, HitTestType type) const
+HitTestResult StackingContext::hit_test(Gfx::FloatPoint const& position, HitTestType type) const
 {
+    // FIXME: Use the transform origin specified in CSS or SVG
+    auto transform_origin = m_box.paint_box()->absolute_position();
+    auto affine_transform = combine_transformations_2d(m_box.computed_values().transformations());
+    auto transformed_position = affine_transform.inverse().value_or({}).map(position - transform_origin) + transform_origin;
+
     // NOTE: Hit testing basically happens in reverse painting order.
     // https://www.w3.org/TR/CSS22/visuren.html#z-index
 
@@ -282,7 +287,7 @@ HitTestResult StackingContext::hit_test(Gfx::IntPoint const& position, HitTestTy
         auto const& child = *m_children[i];
         if (child.m_box.computed_values().z_index().value_or(0) < 0)
             break;
-        auto result = child.hit_test(position, type);
+        auto result = child.hit_test(transformed_position, type);
         if (result.paintable)
             return result;
     }
@@ -291,7 +296,7 @@ HitTestResult StackingContext::hit_test(Gfx::IntPoint const& position, HitTestTy
     // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
     m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) {
         if (box.is_positioned() && !box.paint_box()->stacking_context()) {
-            result = box.paint_box()->hit_test(position, type);
+            result = box.paint_box()->hit_test(transformed_position, type);
             if (result.paintable)
                 return IterationDecision::Break;
         }
@@ -302,7 +307,7 @@ HitTestResult StackingContext::hit_test(Gfx::IntPoint const& position, HitTestTy
 
     // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
     if (m_box.children_are_inline() && is<Layout::BlockContainer>(m_box)) {
-        auto result = m_box.paint_box()->hit_test(position, type);
+        auto result = m_box.paint_box()->hit_test(transformed_position, type);
         if (result.paintable)
             return result;
     }
@@ -310,7 +315,7 @@ HitTestResult StackingContext::hit_test(Gfx::IntPoint const& position, HitTestTy
     // 4. the non-positioned floats.
     m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) {
         if (box.is_floating()) {
-            result = box.paint_box()->hit_test(position, type);
+            result = box.paint_box()->hit_test(transformed_position, type);
             if (result.paintable)
                 return IterationDecision::Break;
         }
@@ -321,7 +326,7 @@ HitTestResult StackingContext::hit_test(Gfx::IntPoint const& position, HitTestTy
     if (!m_box.children_are_inline()) {
         m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) {
             if (!box.is_absolutely_positioned() && !box.is_floating()) {
-                result = box.paint_box()->hit_test(position, type);
+                result = box.paint_box()->hit_test(transformed_position, type);
                 if (result.paintable)
                     return IterationDecision::Break;
             }
@@ -336,13 +341,13 @@ HitTestResult StackingContext::hit_test(Gfx::IntPoint const& position, HitTestTy
         auto const& child = *m_children[i];
         if (child.m_box.computed_values().z_index().value_or(0) < 0)
             break;
-        auto result = child.hit_test(position, type);
+        auto result = child.hit_test(transformed_position, type);
         if (result.paintable)
             return result;
     }
 
     // 1. the background and borders of the element forming the stacking context.
-    if (m_box.paint_box()->absolute_border_box_rect().contains(position.to_type<float>())) {
+    if (m_box.paint_box()->absolute_border_box_rect().contains(transformed_position)) {
         return HitTestResult {
             .paintable = m_box.paintable(),
         };

+ 1 - 1
Userland/Libraries/LibWeb/Painting/StackingContext.h

@@ -30,7 +30,7 @@ public:
 
     void paint_descendants(PaintContext&, Layout::Node&, StackingContextPaintPhase) const;
     void paint(PaintContext&) const;
-    HitTestResult hit_test(Gfx::IntPoint const&, HitTestType) const;
+    HitTestResult hit_test(Gfx::FloatPoint const&, HitTestType) const;
 
     void dump(int indent = 0) const;