| Index: sky/engine/core/rendering/RenderBox.cpp
|
| diff --git a/sky/engine/core/rendering/RenderBox.cpp b/sky/engine/core/rendering/RenderBox.cpp
|
| index 8a0b39b6ae406dbfbce9e87c9e58a23e43cc5710..f1bbc05010c28f1ee64a8b2df4e07809742cc986 100644
|
| --- a/sky/engine/core/rendering/RenderBox.cpp
|
| +++ b/sky/engine/core/rendering/RenderBox.cpp
|
| @@ -39,6 +39,7 @@
|
| #include "sky/engine/core/page/Page.h"
|
| #include "sky/engine/core/rendering/FilterEffectRenderer.h"
|
| #include "sky/engine/core/rendering/HitTestResult.h"
|
| +#include "sky/engine/core/rendering/HitTestingTransformState.h"
|
| #include "sky/engine/core/rendering/PaintInfo.h"
|
| #include "sky/engine/core/rendering/RenderFlexibleBox.h"
|
| #include "sky/engine/core/rendering/RenderGeometryMap.h"
|
| @@ -383,9 +384,261 @@ bool RenderBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result
|
| return false;
|
| }
|
|
|
| +PassRefPtr<HitTestingTransformState> RenderBox::createLocalTransformState(
|
| + RenderLayer* rootLayer, RenderLayer* containerLayer,
|
| + const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation,
|
| + const HitTestingTransformState* containerTransformState) const
|
| +{
|
| + RefPtr<HitTestingTransformState> transformState;
|
| + LayoutPoint offset;
|
| + if (containerTransformState) {
|
| + // If we're already computing transform state, then it's relative to the container (which we know is non-null).
|
| + transformState = HitTestingTransformState::create(*containerTransformState);
|
| + layer()->convertToLayerCoords(containerLayer, offset);
|
| + } else {
|
| + // If this is the first time we need to make transform state, then base it off of hitTestLocation,
|
| + // which is relative to rootLayer.
|
| + transformState = HitTestingTransformState::create(hitTestLocation.transformedPoint(), hitTestLocation.transformedRect(), FloatQuad(hitTestRect));
|
| + layer()->convertToLayerCoords(rootLayer, offset);
|
| + }
|
| +
|
| + RenderObject* containerRenderer = containerLayer ? containerLayer->renderer() : 0;
|
| + if (shouldUseTransformFromContainer(containerRenderer)) {
|
| + TransformationMatrix containerTransform;
|
| + getTransformFromContainer(containerRenderer, toLayoutSize(offset), containerTransform);
|
| + transformState->applyTransform(containerTransform, HitTestingTransformState::AccumulateTransform);
|
| + } else {
|
| + transformState->translate(offset.x(), offset.y(), HitTestingTransformState::AccumulateTransform);
|
| + }
|
| +
|
| + return transformState;
|
| +}
|
| +
|
| +// Compute the z-offset of the point in the transformState.
|
| +// This is effectively projecting a ray normal to the plane of ancestor, finding where that
|
| +// ray intersects target, and computing the z delta between those two points.
|
| +static double computeZOffset(const HitTestingTransformState& transformState)
|
| +{
|
| + // We got an affine transform, so no z-offset
|
| + if (transformState.m_accumulatedTransform.isAffine())
|
| + return 0;
|
| +
|
| + // Flatten the point into the target plane
|
| + FloatPoint targetPoint = transformState.mappedPoint();
|
| +
|
| + // Now map the point back through the transform, which computes Z.
|
| + FloatPoint3D backmappedPoint = transformState.m_accumulatedTransform.mapPoint(FloatPoint3D(targetPoint));
|
| + return backmappedPoint.z();
|
| +}
|
| +
|
| +static bool isHitCandidate(bool canDepthSort, double* zOffset, const HitTestingTransformState* transformState)
|
| +{
|
| + // The hit layer is depth-sorting with other layers, so just say that it was hit.
|
| + if (canDepthSort)
|
| + return true;
|
| +
|
| + // We need to look at z-depth to decide if this layer was hit.
|
| + if (zOffset) {
|
| + ASSERT(transformState);
|
| + // This is actually computing our z, but that's OK because the hitLayer is coplanar with us.
|
| + double childZOffset = computeZOffset(*transformState);
|
| + if (childZOffset > *zOffset) {
|
| + *zOffset = childZOffset;
|
| + return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +static inline bool reverseCompareZIndex(RenderLayerModelObject* first, RenderLayerModelObject* second)
|
| +{
|
| + return first->style()->zIndex() > second->style()->zIndex();
|
| +}
|
| +
|
| +// hitTestLocation and hitTestRect are relative to rootLayer.
|
| +// A 'flattening' layer is one preserves3D() == false.
|
| +// transformState.m_accumulatedTransform holds the transform from the containing flattening layer.
|
| +// transformState.m_lastPlanarPoint is the hitTestLocation in the plane of the containing flattening layer.
|
| +// transformState.m_lastPlanarQuad is the hitTestRect as a quad in the plane of the containing flattening layer.
|
| +//
|
| +// If zOffset is non-null (which indicates that the caller wants z offset information),
|
| +// *zOffset on return is the z offset of the hit point relative to the containing flattening layer.
|
| +bool RenderBox::hitTestLayer(RenderLayer* rootLayer, RenderLayer* containerLayer, const HitTestRequest& request, HitTestResult& result,
|
| + const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation,
|
| + const HitTestingTransformState* transformState, double* zOffset)
|
| +{
|
| + ASSERT(layer()->isSelfPaintingLayer());
|
| +
|
| + // The natural thing would be to keep HitTestingTransformState on the stack, but it's big, so we heap-allocate.
|
| + RefPtr<HitTestingTransformState> localTransformState;
|
| +
|
| + LayoutRect localHitTestRect = hitTestRect;
|
| + HitTestLocation localHitTestLocation = hitTestLocation;
|
| +
|
| + // We need transform state for the first time, or to offset the container state, or to accumulate the new transform.
|
| + if (layer()->transform() || transformState || layer()->has3DTransformedDescendant() || layer()->preserves3D())
|
| + localTransformState = createLocalTransformState(rootLayer, containerLayer, localHitTestRect, localHitTestLocation, transformState);
|
| +
|
| + // Apply a transform if we have one.
|
| + if (layer()->transform()) {
|
| + // Make sure the parent's clip rects have been calculated.
|
| + if (parent()) {
|
| + ClipRect clipRect = layer()->clipper().backgroundClipRect(ClipRectsContext(rootLayer, RootRelativeClipRects));
|
| + // Go ahead and test the enclosing clip now.
|
| + if (!clipRect.intersects(localHitTestLocation))
|
| + return 0;
|
| + }
|
| +
|
| + // If the transform can't be inverted, then don't hit test this layer at all.
|
| + if (!localTransformState->m_accumulatedTransform.isInvertible())
|
| + return 0;
|
| +
|
| + // Compute the point and the hit test rect in the coords of this layer by using the values
|
| + // from the transformState, which store the point and quad in the coords of the last flattened
|
| + // layer, and the accumulated transform which lets up map through preserve-3d layers.
|
| + //
|
| + // We can't just map hitTestLocation and hitTestRect because they may have been flattened (losing z)
|
| + // by our container.
|
| + FloatPoint localPoint = localTransformState->mappedPoint();
|
| + FloatQuad localPointQuad = localTransformState->mappedQuad();
|
| + localHitTestRect = localTransformState->boundsOfMappedArea();
|
| + if (localHitTestLocation.isRectBasedTest())
|
| + localHitTestLocation = HitTestLocation(localPoint, localPointQuad);
|
| + else
|
| + localHitTestLocation = HitTestLocation(localPoint);
|
| +
|
| + // Now do a hit test with the root layer shifted to be us.
|
| + rootLayer = layer();
|
| + }
|
| +
|
| + // Ensure our lists and 3d status are up-to-date.
|
| + layer()->stackingNode()->updateLayerListsIfNeeded();
|
| + layer()->update3DTransformedDescendantStatus();
|
| +
|
| + // Check for hit test on backface if backface-visibility is 'hidden'
|
| + if (localTransformState && style()->backfaceVisibility() == BackfaceVisibilityHidden) {
|
| + TransformationMatrix invertedMatrix = localTransformState->m_accumulatedTransform.inverse();
|
| + // If the z-vector of the matrix is negative, the back is facing towards the viewer.
|
| + if (invertedMatrix.m33() < 0)
|
| + return 0;
|
| + }
|
| +
|
| + RefPtr<HitTestingTransformState> unflattenedTransformState = localTransformState;
|
| + if (localTransformState && !layer()->preserves3D()) {
|
| + // Keep a copy of the pre-flattening state, for computing z-offsets for the container
|
| + unflattenedTransformState = HitTestingTransformState::create(*localTransformState);
|
| + // This layer is flattening, so flatten the state passed to descendants.
|
| + localTransformState->flatten();
|
| + }
|
| +
|
| + // The following are used for keeping track of the z-depth of the hit point of 3d-transformed
|
| + // descendants.
|
| + double localZOffset = -std::numeric_limits<double>::infinity();
|
| + double* zOffsetForDescendantsPtr = 0;
|
| + double* zOffsetForContentsPtr = 0;
|
| +
|
| + bool depthSortDescendants = false;
|
| + if (layer()->preserves3D()) {
|
| + depthSortDescendants = true;
|
| + // Our layers can depth-test with our container, so share the z depth pointer with the container, if it passed one down.
|
| + zOffsetForDescendantsPtr = zOffset ? zOffset : &localZOffset;
|
| + zOffsetForContentsPtr = zOffset ? zOffset : &localZOffset;
|
| + } else if (zOffset) {
|
| + zOffsetForDescendantsPtr = 0;
|
| + // Container needs us to give back a z offset for the hit layer.
|
| + zOffsetForContentsPtr = zOffset;
|
| + }
|
| +
|
| + Vector<RenderBox*> layers;
|
| + collectSelfPaintingLayers(layers);
|
| + std::stable_sort(layers.begin(), layers.end(), reverseCompareZIndex);
|
| +
|
| + bool hitLayer = false;
|
| + for (auto& currentLayer : layers) {
|
| + HitTestResult tempResult(result.hitTestLocation());
|
| + bool localHitLayer = currentLayer->hitTestLayer(rootLayer, layer(), request, tempResult,
|
| + hitTestRect, hitTestLocation, localTransformState.get(), zOffsetForDescendantsPtr);
|
| +
|
| + // If it a rect-based test, we can safely append the temporary result since it might had hit
|
| + // nodes but not necesserily had hitLayer set.
|
| + if (result.isRectBasedTest())
|
| + result.append(tempResult);
|
| +
|
| + if (localHitLayer && isHitCandidate(depthSortDescendants, zOffset, unflattenedTransformState.get())) {
|
| + hitLayer = localHitLayer;
|
| + if (!result.isRectBasedTest())
|
| + result = tempResult;
|
| + if (!depthSortDescendants)
|
| + return true;
|
| + }
|
| + }
|
| +
|
| + LayoutRect layerBounds;
|
| + // FIXME(sky): Remove foregroundRect. It's unused.
|
| + ClipRect contentRect, foregroundRect;
|
| + ClipRectsContext clipRectsContext(rootLayer, RootRelativeClipRects);
|
| + layer()->clipper().calculateRects(clipRectsContext, localHitTestRect, layerBounds, contentRect, foregroundRect);
|
| +
|
| + // Next we want to see if the mouse pos is inside the child RenderObjects of the layer.
|
| + if (contentRect.intersects(localHitTestLocation)) {
|
| + // Hit test with a temporary HitTestResult, because we only want to commit to 'result' if we know we're frontmost.
|
| + HitTestResult tempResult(result.hitTestLocation());
|
| + if (hitTestNonLayerDescendants(request, tempResult, layerBounds, localHitTestLocation)
|
| + && isHitCandidate(false, zOffsetForContentsPtr, unflattenedTransformState.get())) {
|
| + if (result.isRectBasedTest())
|
| + result.append(tempResult);
|
| + else
|
| + result = tempResult;
|
| + if (!depthSortDescendants)
|
| + return true;
|
| + // Foreground can depth-sort with descendant layers, so keep this as a candidate.
|
| + hitLayer = true;
|
| + } else if (result.isRectBasedTest()) {
|
| + result.append(tempResult);
|
| + }
|
| + }
|
| +
|
| + return hitLayer;
|
| +}
|
| +
|
| +bool RenderBox::hitTestNonLayerDescendants(const HitTestRequest& request, HitTestResult& result,
|
| + const LayoutRect& layerBounds, const HitTestLocation& hitTestLocation)
|
| +{
|
| + if (!hitTest(request, result, hitTestLocation, toLayoutPoint(layerBounds.location() - location()))) {
|
| + // It's wrong to set innerNode, but then claim that you didn't hit anything, unless it is
|
| + // a rect-based test.
|
| + ASSERT(!result.innerNode() || (result.isRectBasedTest() && result.rectBasedTestResult().size()));
|
| + return false;
|
| + }
|
| +
|
| + // For positioned generated content, we might still not have a
|
| + // node by the time we get to the layer level, since none of
|
| + // the content in the layer has an element. So just walk up
|
| + // the tree.
|
| + if (!result.innerNode() || !result.innerNonSharedNode()) {
|
| + Node* enclosingElement = 0;
|
| + for (RenderObject* r = this; r; r = r->parent()) {
|
| + if (Node* element = r->node()) {
|
| + enclosingElement = element;
|
| + break;
|
| + }
|
| + }
|
| + ASSERT(enclosingElement);
|
| +
|
| + if (!result.innerNode())
|
| + result.setInnerNode(enclosingElement);
|
| + if (!result.innerNonSharedNode())
|
| + result.setInnerNonSharedNode(enclosingElement);
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| // --------------------- painting stuff -------------------------------
|
|
|
| -static inline bool compareZIndex(RenderBox* first, RenderBox* second)
|
| +static inline bool forwardCompareZIndex(RenderBox* first, RenderBox* second)
|
| {
|
| return first->style()->zIndex() < second->style()->zIndex();
|
| }
|
| @@ -564,7 +817,7 @@ void RenderBox::paintLayerContents(GraphicsContext* context, const LayerPainting
|
| PaintInfo paintInfo(context, pixelSnappedIntRect(contentRect.rect()), localPaintingInfo.rootLayer->renderer());
|
| paint(paintInfo, layerLocation, layers);
|
|
|
| - std::stable_sort(layers.begin(), layers.end(), compareZIndex);
|
| + std::stable_sort(layers.begin(), layers.end(), forwardCompareZIndex);
|
| for (auto& box : layers) {
|
| box->paintLayer(context, paintingInfo.rootLayer, rect);
|
| }
|
|
|