float distanceSquaredToTargetCenterLine(const IntPoint& touchHotspot, const IntRect& touchArea, const SubtargetGeometry& subtarget) { UNUSED_PARAM(touchArea); // For a better center of a line-box we use the center-line instead of the center-point. // We use the center-line of the bounding box of the quad though, since it is much faster // and gives the same result in all untransformed cases, and in transformed cases still // gives a better distance-function than the distance to the center-point. IntRect rect = subtarget.boundingBox(); ASSERT(subtarget.node()->document()); ASSERT(subtarget.node()->document()->view()); // Convert from frame coordinates to window coordinates. rect = subtarget.node()->document()->view()->contentsToWindow(rect); return rect.distanceSquaredFromCenterLineToPoint(touchHotspot); }
// This returns quotient of the target area and its intersection with the touch area. // This will prioritize largest intersection and smallest area, while balancing the two against each other. float zoomableIntersectionQuotient(const IntPoint& touchHotspot, const IntRect& touchArea, const SubtargetGeometry& subtarget) { IntRect rect = subtarget.boundingBox(); // Convert from frame coordinates to window coordinates. rect = subtarget.node()->document()->view()->contentsToWindow(rect); // Check the rectangle is meaningful zoom target. It should at least contain the hotspot. if (!rect.contains(touchHotspot)) return std::numeric_limits<float>::infinity(); IntRect intersection = rect; intersection.intersect(touchArea); // Return the quotient of the intersection. return rect.size().area() / (float)intersection.size().area(); }
bool snapTo(const SubtargetGeometry& geom, const IntPoint& touchPoint, const IntRect& touchArea, IntPoint& adjustedPoint) { FrameView* view = geom.node()->document()->view(); FloatQuad quad = geom.quad(); if (quad.isRectilinear()) { IntRect contentBounds = geom.boundingBox(); // Convert from frame coordinates to window coordinates. IntRect bounds = view->contentsToWindow(contentBounds); if (bounds.contains(touchPoint)) { adjustedPoint = touchPoint; return true; } if (bounds.intersects(touchArea)) { bounds.intersect(touchArea); adjustedPoint = bounds.center(); return true; } return false; } // The following code tries to adjust the point to place inside a both the touchArea and the non-rectilinear quad. // FIXME: This will return the point inside the touch area that is the closest to the quad center, but does not // guarantee that the point will be inside the quad. Corner-cases exist where the quad will intersect but this // will fail to adjust the point to somewhere in the intersection. // Convert quad from content to window coordinates. FloatPoint p1 = contentsToWindow(view, quad.p1()); FloatPoint p2 = contentsToWindow(view, quad.p2()); FloatPoint p3 = contentsToWindow(view, quad.p3()); FloatPoint p4 = contentsToWindow(view, quad.p4()); quad = FloatQuad(p1, p2, p3, p4); if (quad.containsPoint(touchPoint)) { adjustedPoint = touchPoint; return true; } // Pull point towards the center of the element. FloatPoint center = quad.center(); adjustPointToRect(center, touchArea); adjustedPoint = roundedIntPoint(center); return quad.containsPoint(adjustedPoint); }
// Uses a hybrid of distance to adjust and intersect ratio, normalizing each score between 0 and 1 // and combining them. The distance to adjust works best for disambiguating clicks on targets such // as links, where the width may be significantly larger than the touch width. Using area of overlap // in such cases can lead to a bias towards shorter links. Conversely, percentage of overlap can // provide strong confidence in tapping on a small target, where the overlap is often quite high, // and works well for tightly packed controls. float hybridDistanceFunction(const IntPoint& touchHotspot, const IntRect& touchRect, const SubtargetGeometry& subtarget) { IntRect rect = subtarget.boundingBox(); // Convert from frame coordinates to window coordinates. rect = subtarget.node()->document()->view()->contentsToWindow(rect); float radiusSquared = 0.25f * (touchRect.size().diagonalLengthSquared()); float distanceToAdjustScore = rect.distanceSquaredToPoint(touchHotspot) / radiusSquared; int maxOverlapWidth = std::min(touchRect.width(), rect.width()); int maxOverlapHeight = std::min(touchRect.height(), rect.height()); float maxOverlapArea = std::max(maxOverlapWidth * maxOverlapHeight, 1); rect.intersect(touchRect); float intersectArea = rect.size().area(); float intersectionScore = 1 - intersectArea / maxOverlapArea; float hybridScore = intersectionScore + distanceToAdjustScore; return hybridScore; }