SpatialNavigation.cpp [plain text]
#include "config.h"
#include "SpatialNavigation.h"
#include "Frame.h"
#include "FrameTree.h"
#include "FrameView.h"
#include "HTMLFrameOwnerElement.h"
#include "IntRect.h"
#include "Node.h"
#include "Page.h"
namespace WebCore {
static long long spatialDistance(FocusDirection, const IntRect&, const IntRect&);
static IntRect renderRectRelativeToRootDocument(RenderObject*);
static RectsAlignment alignmentForRects(FocusDirection, const IntRect&, const IntRect&);
static bool areRectsFullyAligned(FocusDirection, const IntRect&, const IntRect&);
static bool areRectsPartiallyAligned(FocusDirection, const IntRect&, const IntRect&);
static bool isRectInDirection(FocusDirection, const IntRect&, const IntRect&);
static void deflateIfOverlapped(IntRect&, IntRect&);
static bool checkNegativeCoordsForNode(Node*, const IntRect&);
void distanceDataForNode(FocusDirection direction, Node* start, FocusCandidate& candidate)
{
RenderObject* startRender = start->renderer();
if (!startRender) {
candidate.distance = maxDistance();
return;
}
RenderObject* destRender = candidate.node->renderer();
if (!destRender) {
candidate.distance = maxDistance();
return;
}
IntRect curRect = renderRectRelativeToRootDocument(startRender);
IntRect targetRect = renderRectRelativeToRootDocument(destRender);
deflateIfOverlapped(curRect, targetRect);
if (curRect.isEmpty() || targetRect.isEmpty()
|| targetRect.width() <= 0 || targetRect.height() <= 0) {
candidate.distance = maxDistance();
return;
}
if (!checkNegativeCoordsForNode(start, curRect)) {
candidate.distance = maxDistance();
return;
}
if (!checkNegativeCoordsForNode(candidate.node, targetRect)) {
candidate.distance = maxDistance();
return;
}
if (!isRectInDirection(direction, curRect, targetRect)) {
candidate.distance = maxDistance();
return;
}
candidate.alignment = alignmentForRects(direction, curRect, targetRect);
candidate.distance = spatialDistance(direction, curRect, targetRect);
}
static IntRect renderRectRelativeToRootDocument(RenderObject* render)
{
ASSERT(render);
IntRect rect(render->absoluteClippedOverflowRect());
if (rect.isEmpty()) {
Element* e = static_cast<Element*>(render->node());
rect = e->getRect();
}
Node* node = render->node();
Document* mainDocument = node->document()->page()->mainFrame()->document();
bool considerScrollOffset = !(hasOffscreenRect(node) && node->document() != mainDocument);
if (considerScrollOffset) {
if (FrameView* frameView = render->node()->document()->view())
rect.move(-frameView->scrollOffset());
}
for (Frame* frame = render->document()->frame(); frame; frame = frame->tree()->parent()) {
if (HTMLFrameOwnerElement* ownerElement = frame->ownerElement())
rect.move(ownerElement->offsetLeft(), ownerElement->offsetTop());
}
return rect;
}
static RectsAlignment alignmentForRects(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect)
{
if (areRectsFullyAligned(direction, curRect, targetRect))
return Full;
if (areRectsPartiallyAligned(direction, curRect, targetRect))
return Partial;
return None;
}
static inline bool isHorizontalMove(FocusDirection direction)
{
return direction == FocusDirectionLeft || direction == FocusDirectionRight;
}
static inline int start(FocusDirection direction, const IntRect& rect)
{
return isHorizontalMove(direction) ? rect.y() : rect.x();
}
static inline int middle(FocusDirection direction, const IntRect& rect)
{
IntPoint center(rect.center());
return isHorizontalMove(direction) ? center.y(): center.x();
}
static inline int end(FocusDirection direction, const IntRect& rect)
{
return isHorizontalMove(direction) ? rect.bottom() : rect.right();
}
static bool areRectsFullyAligned(FocusDirection direction, const IntRect& a, const IntRect& b)
{
int aStart, bStart, aEnd, bEnd;
switch (direction) {
case FocusDirectionLeft:
aStart = a.x();
bEnd = b.right();
break;
case FocusDirectionRight:
aStart = b.x();
bEnd = a.right();
break;
case FocusDirectionUp:
aStart = a.y();
bEnd = b.y();
break;
case FocusDirectionDown:
aStart = b.y();
bEnd = a.y();
break;
default:
ASSERT_NOT_REACHED();
return false;
}
if (aStart < bEnd)
return false;
aStart = start(direction, a);
bStart = start(direction, b);
int aMiddle = middle(direction, a);
int bMiddle = middle(direction, b);
aEnd = end(direction, a);
bEnd = end(direction, b);
return ((bMiddle >= aStart && bMiddle <= aEnd) || (aMiddle >= bStart && aMiddle <= bEnd) || (bStart == aStart) || (bEnd == aEnd)); }
static bool areRectsPartiallyAligned(FocusDirection direction, const IntRect& a, const IntRect& b)
{
int aStart = start(direction, a);
int bStart = start(direction, b);
int bMiddle = middle(direction, b);
int aEnd = end(direction, a);
int bEnd = end(direction, b);
return ((bStart >= aStart && bStart <= aEnd)
|| (bStart >= aStart && bStart <= aEnd)
|| (bEnd >= aStart && bEnd <= aEnd)
|| (bMiddle >= aStart && bMiddle <= aEnd)
|| (bEnd >= aStart && bEnd <= aEnd));
}
static inline bool below(const IntRect& a, const IntRect& b)
{
return a.y() > b.bottom();
}
static inline bool rightOf(const IntRect& a, const IntRect& b)
{
return a.x() > b.right();
}
static long long spatialDistance(FocusDirection direction, const IntRect& a, const IntRect& b)
{
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
if (direction == FocusDirectionLeft) {
x1 = a.x();
x2 = b.right();
if (below(a, b)) {
y1 = a.y();
y2 = b.bottom();
} else if (below(b, a)) {
y1 = a.bottom();
y2 = b.y();
} else {
y1 = 0;
y2 = 0;
}
} else if (direction == FocusDirectionRight) {
x1 = a.right();
x2 = b.x();
if (below(a, b)) {
y1 = a.y();
y2 = b.bottom();
} else if (below(b, a)) {
y1 = a.bottom();
y2 = b.y();
} else {
y1 = 0;
y2 = 0;
}
} else if (direction == FocusDirectionUp) {
y1 = a.y();
y2 = b.bottom();
if (rightOf(a, b)) {
x1 = a.x();
x2 = b.right();
} else if (rightOf(b, a)) {
x1 = a.right();
x2 = b.x();
} else {
x1 = 0;
x2 = 0;
}
} else if (direction == FocusDirectionDown) {
y1 = a.bottom();
y2 = b.y();
if (rightOf(a, b)) {
x1 = a.x();
x2 = b.right();
} else if (rightOf(b, a)) {
x1 = a.right();
x2 = b.x();
} else {
x1 = 0;
x2 = 0;
}
}
long long dx = x1 - x2;
long long dy = y1 - y2;
long long distance = (dx * dx) + (dy * dy);
if (distance < 0)
distance *= -1;
return distance;
}
static bool isRectInDirection(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect)
{
IntPoint center(targetRect.center());
int targetMiddle = isHorizontalMove(direction) ? center.x() : center.y();
switch (direction) {
case FocusDirectionLeft:
return targetMiddle < curRect.x();
case FocusDirectionRight:
return targetMiddle > curRect.right();
case FocusDirectionUp:
return targetMiddle < curRect.y();
case FocusDirectionDown:
return targetMiddle > curRect.bottom();
default:
ASSERT_NOT_REACHED();
}
return false;
}
bool hasOffscreenRect(Node* node)
{
FrameView* frameView = node->document()->view();
if (!frameView)
return true;
IntRect containerViewportRect = frameView->visibleContentRect();
RenderObject* render = node->renderer();
if (!render)
return true;
IntRect rect(render->absoluteClippedOverflowRect());
if (rect.isEmpty())
return true;
return !containerViewportRect.intersects(rect);
}
bool scrollInDirection(Frame* frame, FocusDirection direction)
{
if (!frame)
return false;
ScrollDirection scrollDirection;
switch (direction) {
case FocusDirectionLeft:
scrollDirection = ScrollLeft;
break;
case FocusDirectionRight:
scrollDirection = ScrollRight;
break;
case FocusDirectionUp:
scrollDirection = ScrollUp;
break;
case FocusDirectionDown:
scrollDirection = ScrollDown;
break;
default:
return false;
}
return frame->eventHandler()->scrollRecursively(scrollDirection, ScrollByLine);
}
void scrollIntoView(Element* element)
{
static const int fudgeFactor = 2;
IntRect bounds = element->getRect();
bounds.inflate(fudgeFactor);
element->renderer()->enclosingLayer()->scrollRectToVisible(bounds);
}
bool isInRootDocument(Node* node)
{
if (!node)
return false;
Document* rootDocument = node->document()->page()->mainFrame()->document();
return node->document() == rootDocument;
}
static void deflateIfOverlapped(IntRect& a, IntRect& b)
{
if (!a.intersects(b) || a.contains(b) || b.contains(a))
return;
static const int fudgeFactor = -2;
if ((a.width() + 2 * fudgeFactor > 0) && (a.height() + 2 * fudgeFactor > 0))
a.inflate(fudgeFactor);
if ((b.width() + 2 * fudgeFactor > 0) && (b.height() + 2 * fudgeFactor > 0))
b.inflate(fudgeFactor);
}
static bool checkNegativeCoordsForNode(Node* node, const IntRect& curRect)
{
ASSERT(node || node->renderer());
if (curRect.x() > 0 && curRect.y() > 0)
return true;
bool canBeScrolled = false;
RenderObject* renderer = node->renderer();
for (; renderer; renderer = renderer->parent()) {
if (renderer->isBox() && toRenderBox(renderer)->canBeScrolledAndHasScrollableArea()) {
canBeScrolled = true;
break;
}
}
return canBeScrolled;
}
}