2 * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
3 * Copyright (C) 2009 Antonio Gomes <tonikitoo@webkit.org>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
20 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include "SpatialNavigation.h"
33 #include "FrameTree.h"
34 #include "FrameView.h"
35 #include "HTMLAreaElement.h"
36 #include "HTMLImageElement.h"
37 #include "HTMLMapElement.h"
38 #include "HTMLNames.h"
42 #include "RenderInline.h"
43 #include "RenderLayer.h"
48 static RectsAlignment alignmentForRects(FocusDirection, const LayoutRect&, const LayoutRect&, const LayoutSize& viewSize);
49 static bool areRectsFullyAligned(FocusDirection, const LayoutRect&, const LayoutRect&);
50 static bool areRectsPartiallyAligned(FocusDirection, const LayoutRect&, const LayoutRect&);
51 static bool areRectsMoreThanFullScreenApart(FocusDirection, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize);
52 static bool isRectInDirection(FocusDirection, const LayoutRect&, const LayoutRect&);
53 static void deflateIfOverlapped(LayoutRect&, LayoutRect&);
54 static LayoutRect rectToAbsoluteCoordinates(Frame* initialFrame, const LayoutRect&);
55 static void entryAndExitPointsForDirection(FocusDirection, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint);
56 static bool isScrollableNode(const Node*);
58 FocusCandidate::FocusCandidate(Node* node, FocusDirection direction)
61 , enclosingScrollableBox(0)
62 , distance(maxDistance())
63 , parentDistance(maxDistance())
65 , parentAlignment(None)
67 , isOffscreenAfterScrolling(true)
70 ASSERT(node->isElementNode());
72 if (node->hasTagName(HTMLNames::areaTag)) {
73 HTMLAreaElement* area = static_cast<HTMLAreaElement*>(node);
74 HTMLImageElement* image = area->imageElement();
75 if (!image || !image->renderer())
79 rect = virtualRectForAreaElementAndDirection(area, direction);
81 if (!node->renderer())
85 rect = nodeRectInAbsoluteCoordinates(node, true /* ignore border */);
87 #if ENABLE(TIZEN_FOCUS_UI)
88 if (rect.isEmpty() || rect.maxX() < 0 || rect.maxY() < 0) {
95 isOffscreen = hasOffscreenRect(visibleNode);
96 isOffscreenAfterScrolling = hasOffscreenRect(visibleNode, direction);
99 bool isSpatialNavigationEnabled(const Frame* frame)
101 return (frame && frame->settings() && frame->settings()->isSpatialNavigationEnabled());
104 static RectsAlignment alignmentForRects(FocusDirection direction, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize)
106 // If we found a node in full alignment, but it is too far away, ignore it.
107 if (areRectsMoreThanFullScreenApart(direction, curRect, targetRect, viewSize))
110 if (areRectsFullyAligned(direction, curRect, targetRect))
113 if (areRectsPartiallyAligned(direction, curRect, targetRect))
119 static inline bool isHorizontalMove(FocusDirection direction)
121 return direction == FocusDirectionLeft || direction == FocusDirectionRight;
124 static inline LayoutUnit start(FocusDirection direction, const LayoutRect& rect)
126 return isHorizontalMove(direction) ? rect.y() : rect.x();
129 static inline LayoutUnit middle(FocusDirection direction, const LayoutRect& rect)
131 LayoutPoint center(rect.center());
132 return isHorizontalMove(direction) ? center.y(): center.x();
135 static inline LayoutUnit end(FocusDirection direction, const LayoutRect& rect)
137 return isHorizontalMove(direction) ? rect.maxY() : rect.maxX();
140 // This method checks if rects |a| and |b| are fully aligned either vertically or
141 // horizontally. In general, rects whose central point falls between the top or
142 // bottom of each other are considered fully aligned.
143 // Rects that match this criteria are preferable target nodes in move focus changing
145 // * a = Current focused node's rect.
146 // * b = Focus candidate node's rect.
147 static bool areRectsFullyAligned(FocusDirection direction, const LayoutRect& a, const LayoutRect& b)
149 LayoutUnit aStart, bStart, aEnd, bEnd;
152 case FocusDirectionLeft:
156 case FocusDirectionRight:
160 case FocusDirectionUp:
164 case FocusDirectionDown:
169 ASSERT_NOT_REACHED();
176 aStart = start(direction, a);
177 bStart = start(direction, b);
179 #if !ENABLE(TIZEN_FOCUS_UI)
180 LayoutUnit aMiddle = middle(direction, a);
181 LayoutUnit bMiddle = middle(direction, b);
184 aEnd = end(direction, a);
185 bEnd = end(direction, b);
187 // Picture of the totally aligned logic:
189 // Horizontal Vertical Horizontal Vertical
190 // **************************** *****************************
191 // * _ * _ _ _ _ * * _ * _ _ *
192 // * |_| _ * |_|_|_|_| * * _ |_| * |_|_| *
193 // * |_|....|_| * . * * |_|....|_| * . *
194 // * |_| |_| (1) . * * |_| |_| (2) . *
195 // * |_| * _._ * * |_| * _ _._ _ *
196 // * * |_|_| * * * |_|_|_|_| *
198 // **************************** *****************************
200 // Horizontal Vertical Horizontal Vertical
201 // **************************** *****************************
202 // * _......_ * _ _ _ _ * * _ * _ _ _ _ *
203 // * |_| |_| * |_|_|_|_| * * |_| _ * |_|_|_|_| *
204 // * |_| |_| * . * * |_| |_| * . *
205 // * |_| (3) . * * |_|....|_| (4) . *
206 // * * ._ _ * * * _ _. *
207 // * * |_|_| * * * |_|_| *
209 // **************************** *****************************
211 #if ENABLE(TIZEN_FOCUS_UI)
212 return (aStart <= bStart && aEnd >= bEnd) || (bStart <= aStart && bEnd >= aEnd);
214 return ((bMiddle >= aStart && bMiddle <= aEnd) // (1)
215 || (aMiddle >= bStart && aMiddle <= bEnd) // (2)
216 || (bStart == aStart) // (3)
217 || (bEnd == aEnd)); // (4)
221 // This method checks if |start| and |dest| have a partial intersection, either
222 // horizontally or vertically.
223 // * a = Current focused node's rect.
224 // * b = Focus candidate node's rect.
225 static bool areRectsPartiallyAligned(FocusDirection direction, const LayoutRect& a, const LayoutRect& b)
227 LayoutUnit aStart = start(direction, a);
228 LayoutUnit bStart = start(direction, b);
229 LayoutUnit bMiddle = middle(direction, b);
230 LayoutUnit aEnd = end(direction, a);
231 LayoutUnit bEnd = end(direction, b);
233 // Picture of the partially aligned logic:
235 // Horizontal Vertical
236 // ********************************
239 // * |_|.... _ * . . *
241 // * |_|....|_| * ._._ _ *
245 // ********************************
247 // ... and variants of the above cases.
248 return ((bStart >= aStart && bStart <= aEnd)
249 || (bEnd >= aStart && bEnd <= aEnd)
250 || (bMiddle >= aStart && bMiddle <= aEnd)
251 || (bEnd >= aStart && bEnd <= aEnd));
254 static bool areRectsMoreThanFullScreenApart(FocusDirection direction, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize)
256 ASSERT(isRectInDirection(direction, curRect, targetRect));
259 case FocusDirectionLeft:
260 return curRect.x() - targetRect.maxX() > viewSize.width();
261 case FocusDirectionRight:
262 return targetRect.x() - curRect.maxX() > viewSize.width();
263 case FocusDirectionUp:
264 return curRect.y() - targetRect.maxY() > viewSize.height();
265 case FocusDirectionDown:
266 return targetRect.y() - curRect.maxY() > viewSize.height();
268 ASSERT_NOT_REACHED();
273 // Return true if rect |a| is below |b|. False otherwise.
274 static inline bool below(const LayoutRect& a, const LayoutRect& b)
276 return a.y() > b.maxY();
279 // Return true if rect |a| is on the right of |b|. False otherwise.
280 static inline bool rightOf(const LayoutRect& a, const LayoutRect& b)
282 return a.x() > b.maxX();
285 static bool isRectInDirection(FocusDirection direction, const LayoutRect& curRect, const LayoutRect& targetRect)
288 case FocusDirectionLeft:
289 return targetRect.maxX() <= curRect.x();
290 case FocusDirectionRight:
291 return targetRect.x() >= curRect.maxX();
292 case FocusDirectionUp:
293 return targetRect.maxY() <= curRect.y();
294 case FocusDirectionDown:
295 return targetRect.y() >= curRect.maxY();
297 ASSERT_NOT_REACHED();
302 // Checks if |node| is offscreen the visible area (viewport) of its container
303 // document. In case it is, one can scroll in direction or take any different
304 // desired action later on.
305 bool hasOffscreenRect(Node* node, FocusDirection direction)
307 // Get the FrameView in which |node| is (which means the current viewport if |node|
308 // is not in an inner document), so we can check if its content rect is visible
309 // before we actually move the focus to it.
310 FrameView* frameView = node->document()->view();
314 ASSERT(!frameView->needsLayout());
316 LayoutRect containerViewportRect = frameView->visibleContentRect();
317 // We want to select a node if it is currently off screen, but will be
318 // exposed after we scroll. Adjust the viewport to post-scrolling position.
319 // If the container has overflow:hidden, we cannot scroll, so we do not pass direction
320 // and we do not adjust for scrolling.
322 case FocusDirectionLeft:
323 containerViewportRect.setX(containerViewportRect.x() - Scrollbar::pixelsPerLineStep());
324 containerViewportRect.setWidth(containerViewportRect.width() + Scrollbar::pixelsPerLineStep());
326 case FocusDirectionRight:
327 containerViewportRect.setWidth(containerViewportRect.width() + Scrollbar::pixelsPerLineStep());
329 case FocusDirectionUp:
330 containerViewportRect.setY(containerViewportRect.y() - Scrollbar::pixelsPerLineStep());
331 containerViewportRect.setHeight(containerViewportRect.height() + Scrollbar::pixelsPerLineStep());
333 case FocusDirectionDown:
334 containerViewportRect.setHeight(containerViewportRect.height() + Scrollbar::pixelsPerLineStep());
340 RenderObject* render = node->renderer();
344 LayoutRect rect(render->absoluteClippedOverflowRect());
348 return !containerViewportRect.intersects(rect);
351 bool scrollInDirection(Frame* frame, FocusDirection direction)
355 if (frame && canScrollInDirection(frame->document(), direction)) {
359 case FocusDirectionLeft:
360 dx = - Scrollbar::pixelsPerLineStep();
362 case FocusDirectionRight:
363 dx = Scrollbar::pixelsPerLineStep();
365 case FocusDirectionUp:
366 dy = - Scrollbar::pixelsPerLineStep();
368 case FocusDirectionDown:
369 dy = Scrollbar::pixelsPerLineStep();
372 ASSERT_NOT_REACHED();
376 frame->view()->scrollBy(IntSize(dx, dy));
382 bool scrollInDirection(Node* container, FocusDirection direction)
385 if (container->isDocumentNode())
386 return scrollInDirection(static_cast<Document*>(container)->frame(), direction);
388 if (!container->renderBox())
391 if (canScrollInDirection(container, direction)) {
395 case FocusDirectionLeft:
396 dx = - min<LayoutUnit>(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollLeft());
398 case FocusDirectionRight:
399 ASSERT(container->renderBox()->scrollWidth() > (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
400 dx = min<LayoutUnit>(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollWidth() - (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
402 case FocusDirectionUp:
403 dy = - min<LayoutUnit>(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollTop());
405 case FocusDirectionDown:
406 ASSERT(container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
407 dy = min<LayoutUnit>(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
410 ASSERT_NOT_REACHED();
414 container->renderBox()->enclosingLayer()->scrollByRecursively(IntSize(dx, dy));
421 static void deflateIfOverlapped(LayoutRect& a, LayoutRect& b)
423 if (!a.intersects(b) || a.contains(b) || b.contains(a))
426 LayoutUnit deflateFactor = -fudgeFactor();
428 // Avoid negative width or height values.
429 if ((a.width() + 2 * deflateFactor > 0) && (a.height() + 2 * deflateFactor > 0))
430 a.inflate(deflateFactor);
432 if ((b.width() + 2 * deflateFactor > 0) && (b.height() + 2 * deflateFactor > 0))
433 b.inflate(deflateFactor);
436 bool isScrollableNode(const Node* node)
438 ASSERT(!node->isDocumentNode());
443 if (RenderObject* renderer = node->renderer())
444 return renderer->isBox() && toRenderBox(renderer)->canBeScrolledAndHasScrollableArea() && node->hasChildNodes();
449 Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusDirection direction, Node* node)
454 if (parent->isDocumentNode())
455 parent = static_cast<Document*>(parent)->document()->frame()->ownerElement();
457 parent = parent->parentNode();
458 } while (parent && !canScrollInDirection(parent, direction) && !parent->isDocumentNode());
463 bool canScrollInDirection(const Node* container, FocusDirection direction)
466 if (container->isDocumentNode())
467 return canScrollInDirection(static_cast<const Document*>(container)->frame(), direction);
469 if (!isScrollableNode(container))
473 case FocusDirectionLeft:
474 return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() > 0);
475 case FocusDirectionUp:
476 return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() > 0);
477 case FocusDirectionRight:
478 return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() + container->renderBox()->clientWidth() < container->renderBox()->scrollWidth());
479 case FocusDirectionDown:
480 return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() + container->renderBox()->clientHeight() < container->renderBox()->scrollHeight());
482 ASSERT_NOT_REACHED();
487 bool canScrollInDirection(const Frame* frame, FocusDirection direction)
491 ScrollbarMode verticalMode;
492 ScrollbarMode horizontalMode;
493 frame->view()->calculateScrollbarModesForLayout(horizontalMode, verticalMode);
494 if ((direction == FocusDirectionLeft || direction == FocusDirectionRight) && ScrollbarAlwaysOff == horizontalMode)
496 if ((direction == FocusDirectionUp || direction == FocusDirectionDown) && ScrollbarAlwaysOff == verticalMode)
498 LayoutSize size = frame->view()->contentsSize();
499 LayoutSize offset = frame->view()->scrollOffset();
500 LayoutRect rect = frame->view()->visibleContentRect(true);
503 case FocusDirectionLeft:
504 return offset.width() > 0;
505 case FocusDirectionUp:
506 return offset.height() > 0;
507 case FocusDirectionRight:
508 return rect.width() + offset.width() < size.width();
509 case FocusDirectionDown:
510 return rect.height() + offset.height() < size.height();
512 ASSERT_NOT_REACHED();
517 static LayoutRect rectToAbsoluteCoordinates(Frame* initialFrame, const LayoutRect& initialRect)
519 LayoutRect rect = initialRect;
520 for (Frame* frame = initialFrame; frame; frame = frame->tree()->parent()) {
521 #if ENABLE(TIZEN_FOCUS_UI)
522 RenderBoxModelObject* renderer;
523 if (frame->ownerElement() && (renderer = frame->ownerElement()->renderBoxModelObject())) {
525 rect.move(renderer->offsetLeft(), renderer->offsetTop());
526 } while ((renderer = renderer->offsetParent()));
527 rect.move(-frame->view()->scrollOffset());
530 if (Element* element = static_cast<Element*>(frame->ownerElement())) {
532 rect.move(element->offsetLeft(), element->offsetTop());
533 } while ((element = element->offsetParent()));
534 rect.move((-frame->view()->scrollOffset()));
541 LayoutRect nodeRectInAbsoluteCoordinates(Node* node, bool ignoreBorder)
543 ASSERT(node && node->renderer() && !node->document()->view()->needsLayout());
545 if (node->isDocumentNode())
546 return frameRectInAbsoluteCoordinates(static_cast<Document*>(node)->frame());
547 #if ENABLE(TIZEN_FOCUS_UI)
548 LayoutRect rect = node->getRect();
549 rect.intersect(node->renderer()->absoluteClippedOverflowRect());
550 rect = rectToAbsoluteCoordinates(node->document()->frame(), rect);
552 LayoutRect rect = rectToAbsoluteCoordinates(node->document()->frame(), node->getRect());
555 // For authors that use border instead of outline in their CSS, we compensate by ignoring the border when calculating
556 // the rect of the focused element.
558 rect.move(node->renderer()->style()->borderLeftWidth(), node->renderer()->style()->borderTopWidth());
559 rect.setWidth(rect.width() - node->renderer()->style()->borderLeftWidth() - node->renderer()->style()->borderRightWidth());
560 rect.setHeight(rect.height() - node->renderer()->style()->borderTopWidth() - node->renderer()->style()->borderBottomWidth());
565 LayoutRect frameRectInAbsoluteCoordinates(Frame* frame)
567 return rectToAbsoluteCoordinates(frame, frame->view()->visibleContentRect());
570 // This method calculates the exitPoint from the startingRect and the entryPoint into the candidate rect.
571 // The line between those 2 points is the closest distance between the 2 rects.
572 void entryAndExitPointsForDirection(FocusDirection direction, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint)
575 case FocusDirectionLeft:
576 exitPoint.setX(startingRect.x());
577 entryPoint.setX(potentialRect.maxX());
579 case FocusDirectionUp:
580 exitPoint.setY(startingRect.y());
581 entryPoint.setY(potentialRect.maxY());
583 case FocusDirectionRight:
584 exitPoint.setX(startingRect.maxX());
585 entryPoint.setX(potentialRect.x());
587 case FocusDirectionDown:
588 exitPoint.setY(startingRect.maxY());
589 entryPoint.setY(potentialRect.y());
592 ASSERT_NOT_REACHED();
596 case FocusDirectionLeft:
597 case FocusDirectionRight:
598 if (below(startingRect, potentialRect)) {
599 exitPoint.setY(startingRect.y());
600 entryPoint.setY(potentialRect.maxY());
601 } else if (below(potentialRect, startingRect)) {
602 exitPoint.setY(startingRect.maxY());
603 entryPoint.setY(potentialRect.y());
605 exitPoint.setY(max(startingRect.y(), potentialRect.y()));
606 entryPoint.setY(exitPoint.y());
609 case FocusDirectionUp:
610 case FocusDirectionDown:
611 if (rightOf(startingRect, potentialRect)) {
612 exitPoint.setX(startingRect.x());
613 entryPoint.setX(potentialRect.maxX());
614 } else if (rightOf(potentialRect, startingRect)) {
615 exitPoint.setX(startingRect.maxX());
616 entryPoint.setX(potentialRect.x());
618 exitPoint.setX(max(startingRect.x(), potentialRect.x()));
619 entryPoint.setX(exitPoint.x());
623 ASSERT_NOT_REACHED();
627 bool areElementsOnSameLine(const FocusCandidate& firstCandidate, const FocusCandidate& secondCandidate)
629 if (firstCandidate.isNull() || secondCandidate.isNull())
632 if (!firstCandidate.visibleNode->renderer() || !secondCandidate.visibleNode->renderer())
635 if (!firstCandidate.rect.intersects(secondCandidate.rect))
638 if (firstCandidate.focusableNode->hasTagName(HTMLNames::areaTag) || secondCandidate.focusableNode->hasTagName(HTMLNames::areaTag))
641 if (!firstCandidate.visibleNode->renderer()->isRenderInline() || !secondCandidate.visibleNode->renderer()->isRenderInline())
644 if (firstCandidate.visibleNode->renderer()->containingBlock() != secondCandidate.visibleNode->renderer()->containingBlock())
650 void distanceDataForNode(FocusDirection direction, const FocusCandidate& current, FocusCandidate& candidate)
652 if (areElementsOnSameLine(current, candidate)) {
653 if ((direction == FocusDirectionUp && current.rect.y() > candidate.rect.y()) || (direction == FocusDirectionDown && candidate.rect.y() > current.rect.y())) {
654 candidate.distance = 0;
655 candidate.alignment = Full;
660 LayoutRect nodeRect = candidate.rect;
661 LayoutRect currentRect = current.rect;
662 deflateIfOverlapped(currentRect, nodeRect);
664 if (!isRectInDirection(direction, currentRect, nodeRect))
667 LayoutPoint exitPoint;
668 LayoutPoint entryPoint;
669 LayoutUnit sameAxisDistance = 0;
670 LayoutUnit otherAxisDistance = 0;
671 entryAndExitPointsForDirection(direction, currentRect, nodeRect, exitPoint, entryPoint);
674 case FocusDirectionLeft:
675 sameAxisDistance = exitPoint.x() - entryPoint.x();
676 otherAxisDistance = absoluteValue(exitPoint.y() - entryPoint.y());
678 case FocusDirectionUp:
679 sameAxisDistance = exitPoint.y() - entryPoint.y();
680 otherAxisDistance = absoluteValue(exitPoint.x() - entryPoint.x());
682 case FocusDirectionRight:
683 sameAxisDistance = entryPoint.x() - exitPoint.x();
684 otherAxisDistance = absoluteValue(entryPoint.y() - exitPoint.y());
686 case FocusDirectionDown:
687 sameAxisDistance = entryPoint.y() - exitPoint.y();
688 otherAxisDistance = absoluteValue(entryPoint.x() - exitPoint.x());
691 ASSERT_NOT_REACHED();
695 float x = (entryPoint.x() - exitPoint.x()) * (entryPoint.x() - exitPoint.x());
696 float y = (entryPoint.y() - exitPoint.y()) * (entryPoint.y() - exitPoint.y());
698 float euclidianDistance = sqrt(x + y);
700 // Loosely based on http://www.w3.org/TR/WICD/#focus-handling
701 // df = dotDist + dx + dy + 2 * (xdisplacement + ydisplacement) - sqrt(Overlap)
703 float distance = euclidianDistance + sameAxisDistance + 2 * otherAxisDistance;
704 candidate.distance = roundf(distance);
705 LayoutSize viewSize = candidate.visibleNode->document()->page()->mainFrame()->view()->visibleContentRect().size();
706 candidate.alignment = alignmentForRects(direction, currentRect, nodeRect, viewSize);
709 bool canBeScrolledIntoView(FocusDirection direction, const FocusCandidate& candidate)
711 ASSERT(candidate.visibleNode && candidate.isOffscreen);
712 LayoutRect candidateRect = candidate.rect;
713 for (Node* parentNode = candidate.visibleNode->parentNode(); parentNode; parentNode = parentNode->parentNode()) {
714 LayoutRect parentRect = nodeRectInAbsoluteCoordinates(parentNode);
715 if (!candidateRect.intersects(parentRect)) {
716 if (((direction == FocusDirectionLeft || direction == FocusDirectionRight) && parentNode->renderer()->style()->overflowX() == OHIDDEN)
717 || ((direction == FocusDirectionUp || direction == FocusDirectionDown) && parentNode->renderer()->style()->overflowY() == OHIDDEN))
720 if (parentNode == candidate.enclosingScrollableBox)
721 return canScrollInDirection(parentNode, direction);
726 // The starting rect is the rect of the focused node, in document coordinates.
727 // Compose a virtual starting rect if there is no focused node or if it is off screen.
728 // The virtual rect is the edge of the container or frame. We select which
729 // edge depending on the direction of the navigation.
730 LayoutRect virtualRectForDirection(FocusDirection direction, const LayoutRect& startingRect, LayoutUnit width)
732 LayoutRect virtualStartingRect = startingRect;
734 case FocusDirectionLeft:
735 virtualStartingRect.setX(virtualStartingRect.maxX() - width);
736 virtualStartingRect.setWidth(width);
738 case FocusDirectionUp:
739 virtualStartingRect.setY(virtualStartingRect.maxY() - width);
740 virtualStartingRect.setHeight(width);
742 case FocusDirectionRight:
743 virtualStartingRect.setWidth(width);
745 case FocusDirectionDown:
746 virtualStartingRect.setHeight(width);
749 ASSERT_NOT_REACHED();
752 return virtualStartingRect;
755 LayoutRect virtualRectForAreaElementAndDirection(HTMLAreaElement* area, FocusDirection direction)
758 ASSERT(area->imageElement());
759 // Area elements tend to overlap more than other focusable elements. We flatten the rect of the area elements
760 // to minimize the effect of overlapping areas.
761 LayoutRect rect = virtualRectForDirection(direction, rectToAbsoluteCoordinates(area->document()->frame(), area->computeRect(area->imageElement()->renderer())), 1);
765 HTMLFrameOwnerElement* frameOwnerElement(FocusCandidate& candidate)
767 return candidate.isFrameOwnerElement() ? static_cast<HTMLFrameOwnerElement*>(candidate.visibleNode) : 0;
770 } // namespace WebCore