WebNodeHighlightView.mm   [plain text]

/*
* Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#import "WebNodeHighlightView.h"
#import "WebNodeHighlight.h"

#import <WebCore/GraphicsContext.h>
#import <WebCore/InspectorController.h>
#import <wtf/Assertions.h>

#if PLATFORM(IOS_FAMILY)
#import <CoreGraphics/CoreGraphics.h>
#import <WebCore/FloatQuad.h>
#import <WebCore/GeometryUtilities.h>
#import <WebCore/InspectorOverlay.h>
#import <WebCore/WebCoreThread.h>
#endif

using namespace WebCore;

@implementation WebNodeHighlightView

#if PLATFORM(IOS_FAMILY)
- (void)_removeAllLayers
{
for (CAShapeLayer *layer in _layers)
[layer removeFromSuperlayer];
[_layers removeAllObjects];
}
#endif

- (id)initWithWebNodeHighlight:(WebNodeHighlight *)webNodeHighlight
{
self = [self initWithFrame:NSZeroRect];
if (!self)
return nil;

_webNodeHighlight = [webNodeHighlight retain];

#if PLATFORM(IOS_FAMILY)
_layers = [[NSMutableArray alloc] init];
#endif

return self;
}

- (void)dealloc
{
[self detachFromWebNodeHighlight];
#if PLATFORM(IOS_FAMILY)
[self _removeAllLayers];
[_layers release];
#endif
[super dealloc];
}

- (void)detachFromWebNodeHighlight
{
[_webNodeHighlight release];
_webNodeHighlight = nil;
}

- (BOOL)isFlipped
{
return YES;
}

#if !PLATFORM(IOS_FAMILY)
- (void)drawRect:(NSRect)rect
{
if (_webNodeHighlight) {
[NSGraphicsContext saveGraphicsState];

ASSERT([[NSGraphicsContext currentContext] isFlipped]);

GraphicsContext context([[NSGraphicsContext currentContext] CGContext]);
[_webNodeHighlight inspectorController]->drawHighlight(context);
[NSGraphicsContext restoreGraphicsState];
}
}
#else
- (void)_attach:(CALayer *)parent numLayers:(NSUInteger)numLayers
{
ASSERT(numLayers);

// We have the right layers and they are all parented correctly.
if ([_layers count] == numLayers && [[_layers objectAtIndex:0] superlayer] == parent)
return;

// Remove and create new layers.
[self _removeAllLayers];
for (NSUInteger i = 0; i < numLayers; ++i) {
CAShapeLayer *layer = [[CAShapeLayer alloc] init];
[_layers addObject:layer];
[parent addSublayer:layer];
[layer release];
}
}

static bool findIntersectionOnLineBetweenPoints(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& d1, const FloatPoint& d2, FloatPoint& intersection)
{
// Do the lines intersect?
FloatPoint temporaryIntersectionPoint;
if (!findIntersection(p1, p2, d1, d2, temporaryIntersectionPoint))
return false;

// Is the intersection between the two points on the line?
if (p1.x() >= p2.x()) {
if (temporaryIntersectionPoint.x() > p1.x() || temporaryIntersectionPoint.x() < p2.x())
return false;
} else {
if (temporaryIntersectionPoint.x() > p2.x() || temporaryIntersectionPoint.x() < p1.x())
return false;
}
if (p1.y() >= p2.y()) {
if (temporaryIntersectionPoint.y() > p1.y() || temporaryIntersectionPoint.y() < p2.y())
return false;
} else {
if (temporaryIntersectionPoint.y() > p2.y() || temporaryIntersectionPoint.y() < p1.y())
return false;
}

intersection = temporaryIntersectionPoint;
return true;
}

// This quad intersection works because the two quads are known to be at the same
// rotation and clockwise-ness.
static FloatQuad quadIntersection(FloatQuad bounds, FloatQuad toClamp)
{
// Resulting points.
FloatPoint p1, p2, p3, p4;
bool containsPoint1 = false;
bool containsPoint2 = false;
bool containsPoint3 = false;
bool containsPoint4 = false;
bool intersectForPoint1 = false;
bool intersectForPoint2 = false;
bool intersectForPoint3 = false;
bool intersectForPoint4 = false;

// Top / bottom vertical clamping.
if (bounds.containsPoint(toClamp.p1())) {
containsPoint1 = true;
p1 = toClamp.p1();
} else if (!(intersectForPoint1 = findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p2(), toClamp.p1(), toClamp.p4(), p1)))
p1 = toClamp.p1();

if (bounds.containsPoint(toClamp.p2())) {
containsPoint2 = true;
p2 = toClamp.p2();
} else if (!(intersectForPoint2 = findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p2(), toClamp.p2(), toClamp.p3(), p2)))
p2 = toClamp.p2();

if (bounds.containsPoint(toClamp.p3())) {
containsPoint3 = true;
p3 = toClamp.p3();
} else if (!(intersectForPoint3 = findIntersectionOnLineBetweenPoints(bounds.p4(), bounds.p3(), toClamp.p2(), toClamp.p3(), p3)))
p3 = toClamp.p3();

if (bounds.containsPoint(toClamp.p4())) {
containsPoint4 = true;
p4 = toClamp.p4();
} else if (!(intersectForPoint4 = findIntersectionOnLineBetweenPoints(bounds.p4(), bounds.p3(), toClamp.p1(), toClamp.p4(), p4)))
p4 = toClamp.p4();

// If only one of the points intersected on either the top or bottom line then we
// can clamp the other point on that line to the corner of the bounds.
if (!containsPoint1 && intersectForPoint2 && !intersectForPoint1) {
containsPoint1 = true;
p1 = bounds.p1();
} else if (!containsPoint2 && intersectForPoint1 && !intersectForPoint2) {
containsPoint2 = true;
p2 = bounds.p2();
}
if (!containsPoint4 && intersectForPoint3 && !intersectForPoint4) {
containsPoint4 = true;
p4 = bounds.p4();
} else if (!containsPoint3 && intersectForPoint4 && !intersectForPoint3) {
containsPoint3 = true;
p3 = bounds.p3();
}

// Now we only need to perform horizontal clamping for unadjusted points.
if (!containsPoint2 && !intersectForPoint2)
findIntersectionOnLineBetweenPoints(bounds.p2(), bounds.p3(), p1, p2, p2);
if (!containsPoint3 && !intersectForPoint3)
findIntersectionOnLineBetweenPoints(bounds.p2(), bounds.p3(), p4, p3, p3);
if (!containsPoint1 && !intersectForPoint1)
findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p4(), p1, p2, p1);
if (!containsPoint4 && !intersectForPoint4)
findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p4(), p4, p3, p4);

return FloatQuad(p1, p2, p3, p4);
}

static void layerPathWithHole(CAShapeLayer *layer, const FloatQuad& outerQuad, const FloatQuad& holeQuad)
{
// Nothing to show.
if (outerQuad == holeQuad || holeQuad.containsQuad(outerQuad)) {
layer.path = NULL;
return;
}

// If there is a negative margin / padding then the outer box might not
// fully contain the hole box. In such cases we recalculate the hole to
// be the intersection of the two quads.
FloatQuad innerHole;
if (outerQuad.containsQuad(holeQuad))
innerHole = holeQuad;
else
innerHole = quadIntersection(outerQuad, holeQuad);

// Clockwise inside rect (hole), Counter-Clockwise outside rect (fill).
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, 0, innerHole.p1().x(), innerHole.p1().y());
CGPathAddLineToPoint(path, 0, innerHole.p2().x(), innerHole.p2().y());
CGPathAddLineToPoint(path, 0, innerHole.p3().x(), innerHole.p3().y());
CGPathAddLineToPoint(path, 0, innerHole.p4().x(), innerHole.p4().y());
CGPathMoveToPoint(path, 0, outerQuad.p1().x(), outerQuad.p1().y());
CGPathAddLineToPoint(path, 0, outerQuad.p4().x(), outerQuad.p4().y());
CGPathAddLineToPoint(path, 0, outerQuad.p3().x(), outerQuad.p3().y());
CGPathAddLineToPoint(path, 0, outerQuad.p2().x(), outerQuad.p2().y());
layer.path = path;
CGPathRelease(path);
}

static void layerPath(CAShapeLayer *layer, const FloatQuad& outerQuad)
{
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, 0, outerQuad.p1().x(), outerQuad.p1().y());
CGPathAddLineToPoint(path, 0, outerQuad.p4().x(), outerQuad.p4().y());
CGPathAddLineToPoint(path, 0, outerQuad.p3().x(), outerQuad.p3().y());
CGPathAddLineToPoint(path, 0, outerQuad.p2().x(), outerQuad.p2().y());
layer.path = path;
CGPathRelease(path);
}

- (void)_layoutForNodeHighlight:(InspectorOverlay::Highlight*)highlight parent:(CALayer *)parentLayer
{
if (!highlight->quads.size()) {
[self _removeAllLayers];
return;
}

[self _attach:parentLayer numLayers:4];

CAShapeLayer *marginLayer = [_layers objectAtIndex:0];
CAShapeLayer *borderLayer = [_layers objectAtIndex:1];
CAShapeLayer *paddingLayer = [_layers objectAtIndex:2];
CAShapeLayer *contentLayer = [_layers objectAtIndex:3];

FloatQuad marginQuad = highlight->quads[0];
FloatQuad borderQuad = highlight->quads[1];
FloatQuad paddingQuad = highlight->quads[2];
FloatQuad contentQuad = highlight->quads[3];

marginLayer.fillColor = cachedCGColor(highlight->marginColor);
borderLayer.fillColor = cachedCGColor(highlight->borderColor);
paddingLayer.fillColor = cachedCGColor(highlight->paddingColor);
contentLayer.fillColor = cachedCGColor(highlight->contentColor);

layerPathWithHole(marginLayer, marginQuad, borderQuad);
layerPathWithHole(borderLayer, borderQuad, paddingQuad);
layerPathWithHole(paddingLayer, paddingQuad, contentQuad);
layerPath(contentLayer, contentQuad);
}

- (void)_layoutForRectsHighlight:(InspectorOverlay::Highlight*)highlight parent:(CALayer *)parentLayer
{
NSUInteger numLayers = highlight->quads.size();
if (!numLayers) {
[self _removeAllLayers];
return;
}

[self _attach:parentLayer numLayers:numLayers];

CGColorRef contentColor = cachedCGColor(highlight->contentColor);
for (NSUInteger i = 0; i < numLayers; ++i) {
CAShapeLayer *layer = [_layers objectAtIndex:i];
layer.fillColor = contentColor;
layerPath(layer, highlight->quads[i]);
}
}

- (void)layoutSublayers:(CALayer *)parentLayer
{
if (!_webNodeHighlight)
return;

WebThreadLock();

if (![_webNodeHighlight inspectorController])
return;

InspectorOverlay::Highlight h;
[_webNodeHighlight inspectorController]->getHighlight(h, InspectorOverlay::CoordinateSystem::View);

if (h.type == InspectorOverlay::Highlight::Type::Node)
[self _layoutForNodeHighlight:&h parent:parentLayer];
else if (h.type == InspectorOverlay::Highlight::Type::Rects)
[self _layoutForRectsHighlight:&h parent:parentLayer];
}
#endif

- (WebNodeHighlight *)webNodeHighlight
{
return _webNodeHighlight;
}

@end