mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 18:22:45 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									a2331e8dd3
								
							
						
					
					
						commit
						48efdaa8c4
					
				
					 7 changed files with 28 additions and 23 deletions
				
			
		|  | @ -138,7 +138,7 @@ bool EventHandler::handle_mousewheel(const Gfx::IntPoint& position, unsigned int | ||||||
| 
 | 
 | ||||||
|     // FIXME: Support wheel events in nested browsing contexts.
 |     // 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)) |     if (result.paintable && result.paintable->handle_mousewheel({}, position, buttons, modifiers, wheel_delta_x, wheel_delta_y)) | ||||||
|         return true; |         return true; | ||||||
| 
 | 
 | ||||||
|  | @ -164,7 +164,7 @@ bool EventHandler::handle_mouseup(const Gfx::IntPoint& position, unsigned button | ||||||
|     if (m_mouse_event_tracking_layout_node) { |     if (m_mouse_event_tracking_layout_node) { | ||||||
|         paintable = m_mouse_event_tracking_layout_node->paintable(); |         paintable = m_mouse_event_tracking_layout_node->paintable(); | ||||||
|     } else { |     } 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; |         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.
 |         // Things may have changed as a consequence of Layout::Node::handle_mouseup(). Hit test again.
 | ||||||
|         if (!paint_root()) |         if (!paint_root()) | ||||||
|             return true; |             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; |         paintable = result.paintable; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -222,7 +222,7 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt | ||||||
|         if (m_mouse_event_tracking_layout_node) { |         if (m_mouse_event_tracking_layout_node) { | ||||||
|             paintable = m_mouse_event_tracking_layout_node->paintable(); |             paintable = m_mouse_event_tracking_layout_node->paintable(); | ||||||
|         } else { |         } 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) |             if (!result.paintable) | ||||||
|                 return false; |                 return false; | ||||||
|             paintable = result.paintable; |             paintable = result.paintable; | ||||||
|  | @ -300,7 +300,7 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|         if (button == GUI::MouseButton::Primary) { |         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()) { |             if (result.paintable && result.paintable->layout_node().dom_node()) { | ||||||
| 
 | 
 | ||||||
|                 // See if we want to focus something.
 |                 // 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) { |     if (m_mouse_event_tracking_layout_node) { | ||||||
|         paintable = m_mouse_event_tracking_layout_node->paintable(); |         paintable = m_mouse_event_tracking_layout_node->paintable(); | ||||||
|     } else { |     } 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; |         paintable = result.paintable; | ||||||
|         start_index = result.index_in_node; |         start_index = result.index_in_node; | ||||||
|     } |     } | ||||||
|  | @ -408,7 +408,7 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt | ||||||
|                 return true; |                 return true; | ||||||
|         } |         } | ||||||
|         if (m_in_mouse_selection) { |         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()) { |             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)); |                 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 }); |                 layout_root()->set_selection_end({ hit.paintable->layout_node(), hit.index_in_node }); | ||||||
|  |  | ||||||
|  | @ -41,7 +41,7 @@ bool Paintable::handle_mousewheel(Badge<EventHandler>, Gfx::IntPoint const&, uns | ||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| HitTestResult Paintable::hit_test(Gfx::IntPoint const&, HitTestType) const | HitTestResult Paintable::hit_test(Gfx::FloatPoint const&, HitTestType) const | ||||||
| { | { | ||||||
|     VERIFY_NOT_REACHED(); |     VERIFY_NOT_REACHED(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -50,7 +50,7 @@ public: | ||||||
|     virtual void before_children_paint(PaintContext&, PaintPhase) const { } |     virtual void before_children_paint(PaintContext&, PaintPhase) const { } | ||||||
|     virtual void after_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; } |     virtual bool wants_mouse_events() const { return false; } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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()) |     if (layout_box().is_initial_containing_block_box()) | ||||||
|         return stacking_context()->hit_test(position, type); |         return stacking_context()->hit_test(position, type); | ||||||
|  | @ -542,7 +542,7 @@ HitTestResult PaintableBox::hit_test(Gfx::IntPoint const& position, HitTestType | ||||||
|     return result; |     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()) |     if (!layout_box().children_are_inline()) | ||||||
|         return PaintableBox::hit_test(position, type); |         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()) { |         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()) |             if (is<Layout::Box>(fragment.layout_node()) && static_cast<Layout::Box const&>(fragment.layout_node()).paint_box()->stacking_context()) | ||||||
|                 continue; |                 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()) |                 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()->hit_test(position, type); | ||||||
|                 return { fragment.layout_node().paintable(), fragment.text_index_at(position.x()) }; |                 return { fragment.layout_node().paintable(), fragment.text_index_at(position.x()) }; | ||||||
|  |  | ||||||
|  | @ -115,7 +115,7 @@ public: | ||||||
|     virtual void before_children_paint(PaintContext&, PaintPhase) const override; |     virtual void before_children_paint(PaintContext&, PaintPhase) const override; | ||||||
|     virtual void after_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: | protected: | ||||||
|     explicit PaintableBox(Layout::Box const&); |     explicit PaintableBox(Layout::Box const&); | ||||||
|  | @ -164,7 +164,7 @@ public: | ||||||
|     virtual bool wants_mouse_events() const override { return false; } |     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 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: | protected: | ||||||
|     PaintableWithLines(Layout::BlockContainer const&); |     PaintableWithLines(Layout::BlockContainer const&); | ||||||
|  |  | ||||||
|  | @ -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.
 |     // NOTE: Hit testing basically happens in reverse painting order.
 | ||||||
|     // https://www.w3.org/TR/CSS22/visuren.html#z-index
 |     // 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]; |         auto const& child = *m_children[i]; | ||||||
|         if (child.m_box.computed_values().z_index().value_or(0) < 0) |         if (child.m_box.computed_values().z_index().value_or(0) < 0) | ||||||
|             break; |             break; | ||||||
|         auto result = child.hit_test(position, type); |         auto result = child.hit_test(transformed_position, type); | ||||||
|         if (result.paintable) |         if (result.paintable) | ||||||
|             return result; |             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.
 |     // 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) { |     m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) { | ||||||
|         if (box.is_positioned() && !box.paint_box()->stacking_context()) { |         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) |             if (result.paintable) | ||||||
|                 return IterationDecision::Break; |                 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.
 |     // 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)) { |     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) |         if (result.paintable) | ||||||
|             return result; |             return result; | ||||||
|     } |     } | ||||||
|  | @ -310,7 +315,7 @@ HitTestResult StackingContext::hit_test(Gfx::IntPoint const& position, HitTestTy | ||||||
|     // 4. the non-positioned floats.
 |     // 4. the non-positioned floats.
 | ||||||
|     m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) { |     m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) { | ||||||
|         if (box.is_floating()) { |         if (box.is_floating()) { | ||||||
|             result = box.paint_box()->hit_test(position, type); |             result = box.paint_box()->hit_test(transformed_position, type); | ||||||
|             if (result.paintable) |             if (result.paintable) | ||||||
|                 return IterationDecision::Break; |                 return IterationDecision::Break; | ||||||
|         } |         } | ||||||
|  | @ -321,7 +326,7 @@ HitTestResult StackingContext::hit_test(Gfx::IntPoint const& position, HitTestTy | ||||||
|     if (!m_box.children_are_inline()) { |     if (!m_box.children_are_inline()) { | ||||||
|         m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) { |         m_box.for_each_in_subtree_of_type<Layout::Box>([&](Layout::Box const& box) { | ||||||
|             if (!box.is_absolutely_positioned() && !box.is_floating()) { |             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) |                 if (result.paintable) | ||||||
|                     return IterationDecision::Break; |                     return IterationDecision::Break; | ||||||
|             } |             } | ||||||
|  | @ -336,13 +341,13 @@ HitTestResult StackingContext::hit_test(Gfx::IntPoint const& position, HitTestTy | ||||||
|         auto const& child = *m_children[i]; |         auto const& child = *m_children[i]; | ||||||
|         if (child.m_box.computed_values().z_index().value_or(0) < 0) |         if (child.m_box.computed_values().z_index().value_or(0) < 0) | ||||||
|             break; |             break; | ||||||
|         auto result = child.hit_test(position, type); |         auto result = child.hit_test(transformed_position, type); | ||||||
|         if (result.paintable) |         if (result.paintable) | ||||||
|             return result; |             return result; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // 1. the background and borders of the element forming the stacking context.
 |     // 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 { |         return HitTestResult { | ||||||
|             .paintable = m_box.paintable(), |             .paintable = m_box.paintable(), | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  | @ -30,7 +30,7 @@ public: | ||||||
| 
 | 
 | ||||||
|     void paint_descendants(PaintContext&, Layout::Node&, StackingContextPaintPhase) const; |     void paint_descendants(PaintContext&, Layout::Node&, StackingContextPaintPhase) const; | ||||||
|     void paint(PaintContext&) 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; |     void dump(int indent = 0) const; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Simon Wanner
						Simon Wanner