diff --git a/fixtures/html/dom/document_hit_testing_on_overflow.html b/fixtures/html/dom/document_hit_testing_on_overflow.html new file mode 100644 index 000000000..b3c32b014 --- /dev/null +++ b/fixtures/html/dom/document_hit_testing_on_overflow.html @@ -0,0 +1,187 @@ + + + + + + Overflow Hit Testing Test + + + + +
+

Overflow HitTest Test

+

Goal: verify an overflowed child (overflow:visible on parent) remains hit-testable outside the parent's + visual bounds.

+ + +
+

Container

+ + +
+
+ +
+

Logs / Results

+
+
+ Manual test steps: +
    +
  1. Move the pointer onto the "Overflow Button" text (rendered below the parent).
  2. +
  3. Click it: console should show [CLICK] overflow button and a PASS entry "Manual click + event fired".
  4. +
  5. If it cannot be clicked, overflow visible hit-testing is broken.
  6. +
+
+
+ + + + + \ No newline at end of file diff --git a/src/client/layout/layout_box.cpp b/src/client/layout/layout_box.cpp index 3aabb42e0..2aae19307 100644 --- a/src/client/layout/layout_box.cpp +++ b/src/client/layout/layout_box.cpp @@ -12,6 +12,7 @@ namespace client_layout LayoutBox::LayoutBox(shared_ptr node) : LayoutBoxModelObject(node) + , hit_testable_bounding_box_{glm::vec3(0.0f), glm::vec3(0.0f), glm::mat4(1.0f)} { } @@ -318,9 +319,7 @@ namespace client_layout // TODO(yorkie): handle the hit test for the box with overflow. } else - { - overflowBox = physicalBorderBoxRect(); - } + overflowBox = getHitTestableBoundingBox(); if (overflowBox.has_value()) { @@ -360,12 +359,16 @@ namespace client_layout { LayoutBoxModelObject::didComputeLayoutOnce(availableSpace); + // Update the scrollable overflow from the layout results. setScrollableOverflowFromLayoutResults(); if (isScrollContainer()) { getScrollableArea() ->updateAfterLayout(formattingContext().liveFragment()); } + + // Update the hit-testable bounding box. + updateHitTestableBoundingBox(); } void LayoutBox::updateFromStyle() @@ -405,4 +408,47 @@ namespace client_layout setHasValidCachedGeometry(false); // TODO(yorkie): invalidate the cached geometry of the parent. } + + vector> LayoutBox::getChildBoxes() const + { + auto children = virtualChildren(); + if (!children) + return {}; + + vector> childBoxes; + for (const auto &child : *children) + { + if (child->isBox()) + childBoxes.push_back(static_pointer_cast(child)); + } + return childBoxes; + } + + void LayoutBox::updateHitTestableBoundingBox() + { + auto selfBoundingBox = physicalBorderBoxRect(); + glm::vec3 unionMin = selfBoundingBox.minimumWorld; + glm::vec3 unionMax = selfBoundingBox.maximumWorld; + + for (const auto &childBox : getChildBoxes()) + { + if (!childBox->visible()) + continue; + + const geometry::BoundingBox &childBoundingBox = childBox->getHitTestableBoundingBox(); + unionMin.x = min(unionMin.x, childBoundingBox.minimumWorld.x); + unionMin.y = min(unionMin.y, childBoundingBox.minimumWorld.y); + unionMin.z = min(unionMin.z, childBoundingBox.minimumWorld.z); + unionMax.x = max(unionMax.x, childBoundingBox.maximumWorld.x); + unionMax.y = max(unionMax.y, childBoundingBox.maximumWorld.y); + unionMax.z = max(unionMax.z, childBoundingBox.maximumWorld.z); + } + hit_testable_bounding_box_ = geometry::BoundingBox(unionMin, unionMax, glm::mat4(1.0f)); + + // Notify the parent box to update its hit-testable bounding box. + if (parent() && parent()->isBox()) + { + parentBox()->updateHitTestableBoundingBox(); + } + } } diff --git a/src/client/layout/layout_box.hpp b/src/client/layout/layout_box.hpp index 6d942cd51..6e0a22802 100644 --- a/src/client/layout/layout_box.hpp +++ b/src/client/layout/layout_box.hpp @@ -215,8 +215,15 @@ namespace client_layout { return overflow_ != nullptr && overflow_->visualOverflow; } + inline geometry::BoundingBox getHitTestableBoundingBox() const + { + return hit_testable_bounding_box_; + } + void updateHitTestableBoundingBox(); glm::vec3 computeSize() const; + vector> getChildBoxes() const; + void invalidateCachedGeometry(); protected: @@ -224,5 +231,6 @@ namespace client_layout private: std::shared_ptr overflow_; + geometry::BoundingBox hit_testable_bounding_box_; }; }