 Chromium Code Reviews
 Chromium Code Reviews Issue 797463003:
  spatnav: Allow focus move to a close-by not-fully-aligned node over a distant but fully-aligned nod…  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/blink.git@master
    
  
    Issue 797463003:
  spatnav: Allow focus move to a close-by not-fully-aligned node over a distant but fully-aligned nod…  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/blink.git@master| Index: Source/core/page/SpatialNavigation.cpp | 
| diff --git a/Source/core/page/SpatialNavigation.cpp b/Source/core/page/SpatialNavigation.cpp | 
| index e9ea56911a233c090ba660d29a03419265d2e80c..73ba3a6dd316525709cfb9e1843ad4c09f0a7cc6 100644 | 
| --- a/Source/core/page/SpatialNavigation.cpp | 
| +++ b/Source/core/page/SpatialNavigation.cpp | 
| @@ -45,10 +45,6 @@ namespace blink { | 
| using namespace HTMLNames; | 
| -static RectsAlignment alignmentForRects(FocusType, const LayoutRect&, const LayoutRect&, const LayoutSize& viewSize); | 
| -static bool areRectsFullyAligned(FocusType, const LayoutRect&, const LayoutRect&); | 
| -static bool areRectsPartiallyAligned(FocusType, const LayoutRect&, const LayoutRect&); | 
| -static bool areRectsMoreThanFullScreenApart(FocusType, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize); | 
| static bool isRectInDirection(FocusType, const LayoutRect&, const LayoutRect&); | 
| static void deflateIfOverlapped(LayoutRect&, LayoutRect&); | 
| static LayoutRect rectToAbsoluteCoordinates(LocalFrame* initialFrame, const LayoutRect&); | 
| @@ -59,7 +55,6 @@ FocusCandidate::FocusCandidate(Node* node, FocusType type) | 
| , focusableNode(nullptr) | 
| , enclosingScrollableBox(nullptr) | 
| , distance(maxDistance()) | 
| - , alignment(None) | 
| , isOffscreen(true) | 
| , isOffscreenAfterScrolling(true) | 
| { | 
| @@ -92,150 +87,15 @@ bool isSpatialNavigationEnabled(const LocalFrame* frame) | 
| return (frame && frame->settings() && frame->settings()->spatialNavigationEnabled()); | 
| } | 
| -static RectsAlignment alignmentForRects(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize) | 
| +static bool areRectsNotAligned(FocusType type, const LayoutRect& a, const LayoutRect& b) | 
| 
fs
2015/01/12 12:07:23
Not the most obvious name IMHO. Maybe rectsInterse
 
c.shu
2015/01/12 18:03:38
will do.
 | 
| { | 
| - // If we found a node in full alignment, but it is too far away, ignore it. | 
| - if (areRectsMoreThanFullScreenApart(type, curRect, targetRect, viewSize)) | 
| - return None; | 
| - | 
| - if (areRectsFullyAligned(type, curRect, targetRect)) | 
| - return Full; | 
| - | 
| - if (areRectsPartiallyAligned(type, curRect, targetRect)) | 
| - return Partial; | 
| - | 
| - return None; | 
| -} | 
| - | 
| -static inline bool isHorizontalMove(FocusType type) | 
| -{ | 
| - return type == FocusTypeLeft || type == FocusTypeRight; | 
| -} | 
| - | 
| -static inline LayoutUnit start(FocusType type, const LayoutRect& rect) | 
| -{ | 
| - return isHorizontalMove(type) ? rect.y() : rect.x(); | 
| -} | 
| - | 
| -static inline LayoutUnit middle(FocusType type, const LayoutRect& rect) | 
| -{ | 
| - LayoutPoint center(rect.center()); | 
| - return isHorizontalMove(type) ? center.y(): center.x(); | 
| -} | 
| - | 
| -static inline LayoutUnit end(FocusType type, const LayoutRect& rect) | 
| -{ | 
| - return isHorizontalMove(type) ? rect.maxY() : rect.maxX(); | 
| -} | 
| - | 
| -// This method checks if rects |a| and |b| are fully aligned either vertically or | 
| -// horizontally. In general, rects whose central point falls between the top or | 
| -// bottom of each other are considered fully aligned. | 
| -// Rects that match this criteria are preferable target nodes in move focus changing | 
| -// operations. | 
| -// * a = Current focused node's rect. | 
| -// * b = Focus candidate node's rect. | 
| -static bool areRectsFullyAligned(FocusType type, const LayoutRect& a, const LayoutRect& b) | 
| -{ | 
| - LayoutUnit aStart, bStart, aEnd, bEnd; | 
| - | 
| - switch (type) { | 
| - case FocusTypeLeft: | 
| - aStart = a.x(); | 
| - bEnd = b.x(); | 
| - break; | 
| - case FocusTypeRight: | 
| - aStart = b.x(); | 
| - bEnd = a.x(); | 
| - break; | 
| - case FocusTypeUp: | 
| - aStart = a.y(); | 
| - bEnd = b.y(); | 
| - break; | 
| - case FocusTypeDown: | 
| - aStart = b.y(); | 
| - bEnd = a.y(); | 
| - break; | 
| - default: | 
| - ASSERT_NOT_REACHED(); | 
| - return false; | 
| - } | 
| - | 
| - if (aStart < bEnd) | 
| - return false; | 
| - | 
| - aStart = start(type, a); | 
| - bStart = start(type, b); | 
| - | 
| - LayoutUnit aMiddle = middle(type, a); | 
| - LayoutUnit bMiddle = middle(type, b); | 
| - | 
| - aEnd = end(type, a); | 
| - bEnd = end(type, b); | 
| - | 
| - // Picture of the totally aligned logic: | 
| - // | 
| - // Horizontal Vertical Horizontal Vertical | 
| - // **************************** ***************************** | 
| - // * _ * _ _ _ _ * * _ * _ _ * | 
| - // * |_| _ * |_|_|_|_| * * _ |_| * |_|_| * | 
| - // * |_|....|_| * . * * |_|....|_| * . * | 
| - // * |_| |_| (1) . * * |_| |_| (2) . * | 
| - // * |_| * _._ * * |_| * _ _._ _ * | 
| - // * * |_|_| * * * |_|_|_|_| * | 
| - // * * * * * * | 
| - // **************************** ***************************** | 
| - | 
| - return (bMiddle >= aStart && bMiddle <= aEnd) // (1) | 
| - || (aMiddle >= bStart && aMiddle <= bEnd); // (2) | 
| -} | 
| - | 
| -// This method checks if rects |a| and |b| are partially aligned either vertically or | 
| -// horizontally. In general, rects whose either of edges falls between the top or | 
| -// bottom of each other are considered partially-aligned. | 
| -// This is a separate set of conditions from "fully-aligned" and do not include cases | 
| -// that satisfy the former. | 
| -// * a = Current focused node's rect. | 
| -// * b = Focus candidate node's rect. | 
| -static bool areRectsPartiallyAligned(FocusType type, const LayoutRect& a, const LayoutRect& b) | 
| -{ | 
| - LayoutUnit aStart = start(type, a); | 
| - LayoutUnit bStart = start(type, b); | 
| - LayoutUnit aEnd = end(type, a); | 
| - LayoutUnit bEnd = end(type, b); | 
| - | 
| - // Picture of the partially aligned logic: | 
| - // | 
| - // Horizontal Vertical | 
| - // ******************************** | 
| - // * _ * _ _ _ * | 
| - // * |_| * |_|_|_| * | 
| - // * |_|.... _ * . . * | 
| - // * |_| |_| * . . * | 
| - // * |_|....|_| * ._._ _ * | 
| - // * |_| * |_|_|_| * | 
| - // * |_| * * | 
| - // * * * | 
| - // ******************************** | 
| - // | 
| - // ... and variants of the above cases. | 
| - return (bStart >= aStart && bStart <= aEnd) | 
| - || (bEnd >= aStart && bEnd <= aEnd); | 
| -} | 
| - | 
| -static bool areRectsMoreThanFullScreenApart(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize) | 
| -{ | 
| - ASSERT(isRectInDirection(type, curRect, targetRect)); | 
| - | 
| switch (type) { | 
| case FocusTypeLeft: | 
| - return curRect.x() - targetRect.maxX() > viewSize.width(); | 
| case FocusTypeRight: | 
| - return targetRect.x() - curRect.maxX() > viewSize.width(); | 
| + return a.maxY() <= b.y() || a.y() >= b.maxY(); | 
| case FocusTypeUp: | 
| - return curRect.y() - targetRect.maxY() > viewSize.height(); | 
| case FocusTypeDown: | 
| - return targetRect.y() - curRect.maxY() > viewSize.height(); | 
| + return a.maxX() <= b.x() || a.x() >= b.maxX(); | 
| default: | 
| ASSERT_NOT_REACHED(); | 
| return true; | 
| @@ -644,7 +504,6 @@ void distanceDataForNode(FocusType type, const FocusCandidate& current, FocusCan | 
| if (areElementsOnSameLine(current, candidate)) { | 
| if ((type == FocusTypeUp && current.rect.y() > candidate.rect.y()) || (type == FocusTypeDown && candidate.rect.y() > current.rect.y())) { | 
| candidate.distance = 0; | 
| - candidate.alignment = Full; | 
| return; | 
| } | 
| } | 
| @@ -660,22 +519,34 @@ void distanceDataForNode(FocusType type, const FocusCandidate& current, FocusCan | 
| LayoutPoint entryPoint; | 
| entryAndExitPointsForDirection(type, currentRect, nodeRect, exitPoint, entryPoint); | 
| - LayoutUnit xAxis = exitPoint.x() - entryPoint.x(); | 
| - LayoutUnit yAxis = exitPoint.y() - entryPoint.y(); | 
| + LayoutUnit xAxis = (exitPoint.x() - entryPoint.x()).abs(); | 
| + LayoutUnit yAxis = (exitPoint.y() - entryPoint.y()).abs(); | 
| LayoutUnit navigationAxisDistance; | 
| - LayoutUnit orthogonalAxisDistance; | 
| + LayoutUnit weightedOrthogonalAxisDistance; | 
| + | 
| + // Weights are put to the orthogonal axis distance calculation so more aligned candidate | 
| + // would have advantage over partially- or not-aligned elements. The weight for left/right | 
| + // direction is given a higher value to allow navigation on common horizonally-aligned | 
| + // elements. Note the hardcoded values are based on tests and experiments. | 
| + // The not-aligned node is also given a penalty as the orthogonal distance could be 0. | 
| + const int orthogonalWeightForLeftRight = 30; | 
| + const int orthogonalWeightForUpDown = 2; | 
| switch (type) { | 
| case FocusTypeLeft: | 
| case FocusTypeRight: | 
| - navigationAxisDistance = xAxis.abs(); | 
| - orthogonalAxisDistance = yAxis.abs(); | 
| + navigationAxisDistance = xAxis; | 
| + if (areRectsNotAligned(type, currentRect, nodeRect)) | 
| + yAxis += currentRect.height() / 2; | 
| 
fs
2015/01/12 12:07:23
Why mutate the yAxis value here? Shouldn't you jus
 
c.shu
2015/01/12 18:03:38
The weight has no impact on 0. That's why I have t
 
fs
2015/01/13 09:26:04
I get the part about 0, but why not for instance (
 | 
| + weightedOrthogonalAxisDistance = yAxis * orthogonalWeightForLeftRight; | 
| break; | 
| case FocusTypeUp: | 
| case FocusTypeDown: | 
| - navigationAxisDistance = yAxis.abs(); | 
| - orthogonalAxisDistance = xAxis.abs(); | 
| + navigationAxisDistance = yAxis; | 
| + if (areRectsNotAligned(type, currentRect, nodeRect)) | 
| + xAxis += currentRect.width() / 2; | 
| + weightedOrthogonalAxisDistance = xAxis * orthogonalWeightForUpDown; | 
| break; | 
| default: | 
| ASSERT_NOT_REACHED(); | 
| @@ -687,10 +558,7 @@ void distanceDataForNode(FocusType type, const FocusCandidate& current, FocusCan | 
| double overlap = (intersectionRect.width() * intersectionRect.height()).toDouble(); | 
| // Distance calculation is based on http://www.w3.org/TR/WICD/#focus-handling | 
| - candidate.distance = sqrt(euclidianDistancePow2) + navigationAxisDistance+ orthogonalAxisDistance * 2 - sqrt(overlap); | 
| - | 
| - LayoutSize viewSize = LayoutSize(candidate.visibleNode->document().page()->deprecatedLocalMainFrame()->view()->visibleContentRect().size()); | 
| - candidate.alignment = alignmentForRects(type, currentRect, nodeRect, viewSize); | 
| + candidate.distance = sqrt(euclidianDistancePow2) + navigationAxisDistance + weightedOrthogonalAxisDistance - sqrt(overlap); | 
| } | 
| bool canBeScrolledIntoView(FocusType type, const FocusCandidate& candidate) |