1/* 2 * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#import "WebNodeHighlightView.h" 30#import "WebNodeHighlight.h" 31 32#import <WebCore/GraphicsContext.h> 33#import <WebCore/InspectorController.h> 34#import <wtf/Assertions.h> 35 36#if PLATFORM(IOS) 37#import <CoreGraphics/CoreGraphics.h> 38#import <WebCore/FloatQuad.h> 39#import <WebCore/GeometryUtilities.h> 40#import <WebCore/InspectorOverlay.h> 41#import <WebCore/WebCoreThread.h> 42#endif 43 44using namespace WebCore; 45 46@implementation WebNodeHighlightView 47 48#if PLATFORM(IOS) 49- (void)_removeAllLayers 50{ 51 for (CAShapeLayer *layer in _layers) 52 [layer removeFromSuperlayer]; 53 [_layers removeAllObjects]; 54} 55#endif 56 57- (id)initWithWebNodeHighlight:(WebNodeHighlight *)webNodeHighlight 58{ 59 self = [self initWithFrame:NSZeroRect]; 60 if (!self) 61 return nil; 62 63 _webNodeHighlight = [webNodeHighlight retain]; 64 65#if PLATFORM(IOS) 66 _layers = [[NSMutableArray alloc] init]; 67#endif 68 69 return self; 70} 71 72- (void)dealloc 73{ 74 [self detachFromWebNodeHighlight]; 75#if PLATFORM(IOS) 76 [self _removeAllLayers]; 77 [_layers release]; 78#endif 79 [super dealloc]; 80} 81 82- (void)detachFromWebNodeHighlight 83{ 84 [_webNodeHighlight release]; 85 _webNodeHighlight = nil; 86} 87 88- (BOOL)isFlipped 89{ 90 return YES; 91} 92 93#if !PLATFORM(IOS) 94- (void)drawRect:(NSRect)rect 95{ 96 if (_webNodeHighlight) { 97 [NSGraphicsContext saveGraphicsState]; 98 99 ASSERT([[NSGraphicsContext currentContext] isFlipped]); 100 101 GraphicsContext context((PlatformGraphicsContext*)[[NSGraphicsContext currentContext] graphicsPort]); 102 [_webNodeHighlight inspectorController]->drawHighlight(context); 103 [NSGraphicsContext restoreGraphicsState]; 104 } 105} 106#else 107- (void)_attach:(CALayer *)parent numLayers:(NSUInteger)numLayers 108{ 109 ASSERT(numLayers); 110 111 // We have the right layers and they are all parented correctly. 112 if ([_layers count] == numLayers && [[_layers objectAtIndex:0] superlayer] == parent) 113 return; 114 115 // Remove and create new layers. 116 [self _removeAllLayers]; 117 for (NSUInteger i = 0; i < numLayers; ++i) { 118 CAShapeLayer *layer = [[CAShapeLayer alloc] init]; 119 [_layers addObject:layer]; 120 [parent addSublayer:layer]; 121 [layer release]; 122 } 123} 124 125static bool findIntersectionOnLineBetweenPoints(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& d1, const FloatPoint& d2, FloatPoint& intersection) 126{ 127 // Do the lines intersect? 128 FloatPoint temporaryIntersectionPoint; 129 if (!findIntersection(p1, p2, d1, d2, temporaryIntersectionPoint)) 130 return false; 131 132 // Is the intersection between the two points on the line? 133 if (p1.x() >= p2.x()) { 134 if (temporaryIntersectionPoint.x() > p1.x() || temporaryIntersectionPoint.x() < p2.x()) 135 return false; 136 } else { 137 if (temporaryIntersectionPoint.x() > p2.x() || temporaryIntersectionPoint.x() < p1.x()) 138 return false; 139 } 140 if (p1.y() >= p2.y()) { 141 if (temporaryIntersectionPoint.y() > p1.y() || temporaryIntersectionPoint.y() < p2.y()) 142 return false; 143 } else { 144 if (temporaryIntersectionPoint.y() > p2.y() || temporaryIntersectionPoint.y() < p1.y()) 145 return false; 146 } 147 148 intersection = temporaryIntersectionPoint; 149 return true; 150} 151 152// This quad intersection works because the two quads are known to be at the same 153// rotation and clockwise-ness. 154static FloatQuad quadIntersection(FloatQuad bounds, FloatQuad toClamp) 155{ 156 // Resulting points. 157 FloatPoint p1, p2, p3, p4; 158 bool containsPoint1 = false; 159 bool containsPoint2 = false; 160 bool containsPoint3 = false; 161 bool containsPoint4 = false; 162 bool intersectForPoint1 = false; 163 bool intersectForPoint2 = false; 164 bool intersectForPoint3 = false; 165 bool intersectForPoint4 = false; 166 167 // Top / bottom vertical clamping. 168 if (bounds.containsPoint(toClamp.p1())) { 169 containsPoint1 = true; 170 p1 = toClamp.p1(); 171 } else if (!(intersectForPoint1 = findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p2(), toClamp.p1(), toClamp.p4(), p1))) 172 p1 = toClamp.p1(); 173 174 if (bounds.containsPoint(toClamp.p2())) { 175 containsPoint2 = true; 176 p2 = toClamp.p2(); 177 } else if (!(intersectForPoint2 = findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p2(), toClamp.p2(), toClamp.p3(), p2))) 178 p2 = toClamp.p2(); 179 180 if (bounds.containsPoint(toClamp.p3())) { 181 containsPoint3 = true; 182 p3 = toClamp.p3(); 183 } else if (!(intersectForPoint3 = findIntersectionOnLineBetweenPoints(bounds.p4(), bounds.p3(), toClamp.p2(), toClamp.p3(), p3))) 184 p3 = toClamp.p3(); 185 186 if (bounds.containsPoint(toClamp.p4())) { 187 containsPoint4 = true; 188 p4 = toClamp.p4(); 189 } else if (!(intersectForPoint4 = findIntersectionOnLineBetweenPoints(bounds.p4(), bounds.p3(), toClamp.p1(), toClamp.p4(), p4))) 190 p4 = toClamp.p4(); 191 192 // If only one of the points intersected on either the top or bottom line then we 193 // can clamp the other point on that line to the corner of the bounds. 194 if (!containsPoint1 && intersectForPoint2 && !intersectForPoint1) { 195 containsPoint1 = true; 196 p1 = bounds.p1(); 197 } else if (!containsPoint2 && intersectForPoint1 && !intersectForPoint2) { 198 containsPoint2 = true; 199 p2 = bounds.p2(); 200 } 201 if (!containsPoint4 && intersectForPoint3 && !intersectForPoint4) { 202 containsPoint4 = true; 203 p4 = bounds.p4(); 204 } else if (!containsPoint3 && intersectForPoint4 && !intersectForPoint3) { 205 containsPoint3 = true; 206 p3 = bounds.p3(); 207 } 208 209 // Now we only need to perform horizontal clamping for unadjusted points. 210 if (!containsPoint2 && !intersectForPoint2) 211 findIntersectionOnLineBetweenPoints(bounds.p2(), bounds.p3(), p1, p2, p2); 212 if (!containsPoint3 && !intersectForPoint3) 213 findIntersectionOnLineBetweenPoints(bounds.p2(), bounds.p3(), p4, p3, p3); 214 if (!containsPoint1 && !intersectForPoint1) 215 findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p4(), p1, p2, p1); 216 if (!containsPoint4 && !intersectForPoint4) 217 findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p4(), p4, p3, p4); 218 219 return FloatQuad(p1, p2, p3, p4); 220} 221 222static void layerPathWithHole(CAShapeLayer *layer, const FloatQuad& outerQuad, const FloatQuad& holeQuad) 223{ 224 // Nothing to show. 225 if (outerQuad == holeQuad || holeQuad.containsQuad(outerQuad)) { 226 layer.path = NULL; 227 return; 228 } 229 230 // If there is a negative margin / padding then the outer box might not 231 // fully contain the hole box. In such cases we recalculate the hole to 232 // be the intersection of the two quads. 233 FloatQuad innerHole; 234 if (outerQuad.containsQuad(holeQuad)) 235 innerHole = holeQuad; 236 else 237 innerHole = quadIntersection(outerQuad, holeQuad); 238 239 // Clockwise inside rect (hole), Counter-Clockwise outside rect (fill). 240 CGMutablePathRef path = CGPathCreateMutable(); 241 CGPathMoveToPoint(path, 0, innerHole.p1().x(), innerHole.p1().y()); 242 CGPathAddLineToPoint(path, 0, innerHole.p2().x(), innerHole.p2().y()); 243 CGPathAddLineToPoint(path, 0, innerHole.p3().x(), innerHole.p3().y()); 244 CGPathAddLineToPoint(path, 0, innerHole.p4().x(), innerHole.p4().y()); 245 CGPathMoveToPoint(path, 0, outerQuad.p1().x(), outerQuad.p1().y()); 246 CGPathAddLineToPoint(path, 0, outerQuad.p4().x(), outerQuad.p4().y()); 247 CGPathAddLineToPoint(path, 0, outerQuad.p3().x(), outerQuad.p3().y()); 248 CGPathAddLineToPoint(path, 0, outerQuad.p2().x(), outerQuad.p2().y()); 249 layer.path = path; 250 CGPathRelease(path); 251} 252 253static void layerPath(CAShapeLayer *layer, const FloatQuad& outerQuad) 254{ 255 CGMutablePathRef path = CGPathCreateMutable(); 256 CGPathMoveToPoint(path, 0, outerQuad.p1().x(), outerQuad.p1().y()); 257 CGPathAddLineToPoint(path, 0, outerQuad.p4().x(), outerQuad.p4().y()); 258 CGPathAddLineToPoint(path, 0, outerQuad.p3().x(), outerQuad.p3().y()); 259 CGPathAddLineToPoint(path, 0, outerQuad.p2().x(), outerQuad.p2().y()); 260 layer.path = path; 261 CGPathRelease(path); 262} 263 264- (void)_layoutForNodeHighlight:(Highlight*)h parent:(CALayer *)parentLayer 265{ 266 [self _attach:parentLayer numLayers:4]; 267 268 CAShapeLayer *marginLayer = [_layers objectAtIndex:0]; 269 CAShapeLayer *borderLayer = [_layers objectAtIndex:1]; 270 CAShapeLayer *paddingLayer = [_layers objectAtIndex:2]; 271 CAShapeLayer *contentLayer = [_layers objectAtIndex:3]; 272 273 FloatQuad marginQuad = h->quads[0]; 274 FloatQuad borderQuad = h->quads[1]; 275 FloatQuad paddingQuad = h->quads[2]; 276 FloatQuad contentQuad = h->quads[3]; 277 278 marginLayer.fillColor = cachedCGColor(h->marginColor, ColorSpaceDeviceRGB); 279 borderLayer.fillColor = cachedCGColor(h->borderColor, ColorSpaceDeviceRGB); 280 paddingLayer.fillColor = cachedCGColor(h->paddingColor, ColorSpaceDeviceRGB); 281 contentLayer.fillColor = cachedCGColor(h->contentColor, ColorSpaceDeviceRGB); 282 283 layerPathWithHole(marginLayer, marginQuad, borderQuad); 284 layerPathWithHole(borderLayer, borderQuad, paddingQuad); 285 layerPathWithHole(paddingLayer, paddingQuad, contentQuad); 286 layerPath(contentLayer, contentQuad); 287} 288 289- (void)_layoutForRectsHighlight:(Highlight*)h parent:(CALayer *)parentLayer 290{ 291 NSUInteger numLayers = (NSUInteger)h->quads.size(); 292 if (!numLayers) { 293 [self _removeAllLayers]; 294 return; 295 } 296 297 [self _attach:parentLayer numLayers:numLayers]; 298 299 CGColorRef contentColor = cachedCGColor(h->contentColor, ColorSpaceDeviceRGB); 300 for (NSUInteger i = 0; i < numLayers; ++i) { 301 CAShapeLayer *layer = [_layers objectAtIndex:i]; 302 layer.fillColor = contentColor; 303 layerPath(layer, h->quads[i]); 304 } 305} 306 307- (void)layoutSublayers:(CALayer *)parentLayer 308{ 309 if (!_webNodeHighlight) 310 return; 311 312 WebThreadLock(); 313 314 if (![_webNodeHighlight inspectorController]) 315 return; 316 317 Highlight h; 318 [_webNodeHighlight inspectorController]->getHighlight(&h, InspectorOverlay::CoordinateSystem::View); 319 320 if (h.type == HighlightTypeNode) 321 [self _layoutForNodeHighlight:&h parent:parentLayer]; 322 else if (h.type == HighlightTypeRects) 323 [self _layoutForRectsHighlight:&h parent:parentLayer]; 324} 325#endif 326 327- (WebNodeHighlight *)webNodeHighlight 328{ 329 return _webNodeHighlight; 330} 331 332@end 333