1/* 2 * Copyright (C) 2014 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 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#import "config.h" 27#import "WKInspectorHighlightView.h" 28 29#if PLATFORM(IOS) 30 31#import <WebCore/FloatQuad.h> 32#import <WebCore/GeometryUtilities.h> 33#import <WebCore/InspectorOverlay.h> 34 35using namespace WebCore; 36 37@implementation WKInspectorHighlightView 38 39- (instancetype)initWithFrame:(CGRect)frame 40{ 41 if (!(self = [super initWithFrame:frame])) 42 return nil; 43 _layers = [[NSMutableArray alloc] init]; 44 return self; 45} 46 47- (void)dealloc 48{ 49 [self _removeAllLayers]; 50 [_layers release]; 51 [super dealloc]; 52} 53 54- (void)_removeAllLayers 55{ 56 for (CAShapeLayer *layer in _layers) 57 [layer removeFromSuperlayer]; 58 [_layers removeAllObjects]; 59} 60 61- (void)_createLayers:(NSUInteger)numLayers 62{ 63 if ([_layers count] == numLayers) 64 return; 65 66 [self _removeAllLayers]; 67 68 for (NSUInteger i = 0; i < numLayers; ++i) { 69 CAShapeLayer *layer = [[CAShapeLayer alloc] init]; 70 [_layers addObject:layer]; 71 [self.layer addSublayer:layer]; 72 [layer release]; 73 } 74} 75 76static bool findIntersectionOnLineBetweenPoints(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& d1, const FloatPoint& d2, FloatPoint& intersection) 77{ 78 // Do the lines intersect? 79 FloatPoint temporaryIntersectionPoint; 80 if (!findIntersection(p1, p2, d1, d2, temporaryIntersectionPoint)) 81 return false; 82 83 // Is the intersection between the two points on the line? 84 if (p1.x() >= p2.x()) { 85 if (temporaryIntersectionPoint.x() > p1.x() || temporaryIntersectionPoint.x() < p2.x()) 86 return false; 87 } else { 88 if (temporaryIntersectionPoint.x() > p2.x() || temporaryIntersectionPoint.x() < p1.x()) 89 return false; 90 } 91 if (p1.y() >= p2.y()) { 92 if (temporaryIntersectionPoint.y() > p1.y() || temporaryIntersectionPoint.y() < p2.y()) 93 return false; 94 } else { 95 if (temporaryIntersectionPoint.y() > p2.y() || temporaryIntersectionPoint.y() < p1.y()) 96 return false; 97 } 98 99 intersection = temporaryIntersectionPoint; 100 return true; 101} 102 103// This quad intersection works because the two quads are known to be at the same 104// rotation and clockwise-ness. 105static FloatQuad quadIntersection(FloatQuad bounds, FloatQuad toClamp) 106{ 107 // Resulting points. 108 FloatPoint p1, p2, p3, p4; 109 bool containsPoint1 = false; 110 bool containsPoint2 = false; 111 bool containsPoint3 = false; 112 bool containsPoint4 = false; 113 bool intersectForPoint1 = false; 114 bool intersectForPoint2 = false; 115 bool intersectForPoint3 = false; 116 bool intersectForPoint4 = false; 117 118 // Top / bottom vertical clamping. 119 if (bounds.containsPoint(toClamp.p1())) { 120 containsPoint1 = true; 121 p1 = toClamp.p1(); 122 } else if (!(intersectForPoint1 = findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p2(), toClamp.p1(), toClamp.p4(), p1))) 123 p1 = toClamp.p1(); 124 125 if (bounds.containsPoint(toClamp.p2())) { 126 containsPoint2 = true; 127 p2 = toClamp.p2(); 128 } else if (!(intersectForPoint2 = findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p2(), toClamp.p2(), toClamp.p3(), p2))) 129 p2 = toClamp.p2(); 130 131 if (bounds.containsPoint(toClamp.p3())) { 132 containsPoint3 = true; 133 p3 = toClamp.p3(); 134 } else if (!(intersectForPoint3 = findIntersectionOnLineBetweenPoints(bounds.p4(), bounds.p3(), toClamp.p2(), toClamp.p3(), p3))) 135 p3 = toClamp.p3(); 136 137 if (bounds.containsPoint(toClamp.p4())) { 138 containsPoint4 = true; 139 p4 = toClamp.p4(); 140 } else if (!(intersectForPoint4 = findIntersectionOnLineBetweenPoints(bounds.p4(), bounds.p3(), toClamp.p1(), toClamp.p4(), p4))) 141 p4 = toClamp.p4(); 142 143 // If only one of the points intersected on either the top or bottom line then we 144 // can clamp the other point on that line to the corner of the bounds. 145 if (!containsPoint1 && intersectForPoint2 && !intersectForPoint1) { 146 containsPoint1 = true; 147 p1 = bounds.p1(); 148 } else if (!containsPoint2 && intersectForPoint1 && !intersectForPoint2) { 149 containsPoint2 = true; 150 p2 = bounds.p2(); 151 } 152 if (!containsPoint4 && intersectForPoint3 && !intersectForPoint4) { 153 containsPoint4 = true; 154 p4 = bounds.p4(); 155 } else if (!containsPoint3 && intersectForPoint4 && !intersectForPoint3) { 156 containsPoint3 = true; 157 p3 = bounds.p3(); 158 } 159 160 // Now we only need to perform horizontal clamping for unadjusted points. 161 if (!containsPoint2 && !intersectForPoint2) 162 findIntersectionOnLineBetweenPoints(bounds.p2(), bounds.p3(), p1, p2, p2); 163 if (!containsPoint3 && !intersectForPoint3) 164 findIntersectionOnLineBetweenPoints(bounds.p2(), bounds.p3(), p4, p3, p3); 165 if (!containsPoint1 && !intersectForPoint1) 166 findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p4(), p1, p2, p1); 167 if (!containsPoint4 && !intersectForPoint4) 168 findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p4(), p4, p3, p4); 169 170 return FloatQuad(p1, p2, p3, p4); 171} 172 173static void layerPathWithHole(CAShapeLayer *layer, const FloatQuad& outerQuad, const FloatQuad& holeQuad) 174{ 175 // Nothing to show. 176 if (outerQuad == holeQuad || holeQuad.containsQuad(outerQuad)) { 177 layer.path = NULL; 178 return; 179 } 180 181 // If there is a negative margin / padding then the outer box might not 182 // fully contain the hole box. In such cases we recalculate the hole to 183 // be the intersection of the two quads. 184 FloatQuad innerHole; 185 if (outerQuad.containsQuad(holeQuad)) 186 innerHole = holeQuad; 187 else 188 innerHole = quadIntersection(outerQuad, holeQuad); 189 190 // Clockwise inside rect (hole), Counter-Clockwise outside rect (fill). 191 CGMutablePathRef path = CGPathCreateMutable(); 192 CGPathMoveToPoint(path, 0, innerHole.p1().x(), innerHole.p1().y()); 193 CGPathAddLineToPoint(path, 0, innerHole.p2().x(), innerHole.p2().y()); 194 CGPathAddLineToPoint(path, 0, innerHole.p3().x(), innerHole.p3().y()); 195 CGPathAddLineToPoint(path, 0, innerHole.p4().x(), innerHole.p4().y()); 196 CGPathMoveToPoint(path, 0, outerQuad.p1().x(), outerQuad.p1().y()); 197 CGPathAddLineToPoint(path, 0, outerQuad.p4().x(), outerQuad.p4().y()); 198 CGPathAddLineToPoint(path, 0, outerQuad.p3().x(), outerQuad.p3().y()); 199 CGPathAddLineToPoint(path, 0, outerQuad.p2().x(), outerQuad.p2().y()); 200 layer.path = path; 201 CGPathRelease(path); 202} 203 204static void layerPath(CAShapeLayer *layer, const FloatQuad& outerQuad) 205{ 206 CGMutablePathRef path = CGPathCreateMutable(); 207 CGPathMoveToPoint(path, 0, outerQuad.p1().x(), outerQuad.p1().y()); 208 CGPathAddLineToPoint(path, 0, outerQuad.p4().x(), outerQuad.p4().y()); 209 CGPathAddLineToPoint(path, 0, outerQuad.p3().x(), outerQuad.p3().y()); 210 CGPathAddLineToPoint(path, 0, outerQuad.p2().x(), outerQuad.p2().y()); 211 layer.path = path; 212 CGPathRelease(path); 213} 214 215- (void)_layoutForNodeHighlight:(const Highlight&)highlight 216{ 217 [self _createLayers:4]; 218 219 CAShapeLayer *marginLayer = [_layers objectAtIndex:0]; 220 CAShapeLayer *borderLayer = [_layers objectAtIndex:1]; 221 CAShapeLayer *paddingLayer = [_layers objectAtIndex:2]; 222 CAShapeLayer *contentLayer = [_layers objectAtIndex:3]; 223 224 FloatQuad marginQuad = highlight.quads[0]; 225 FloatQuad borderQuad = highlight.quads[1]; 226 FloatQuad paddingQuad = highlight.quads[2]; 227 FloatQuad contentQuad = highlight.quads[3]; 228 229 marginLayer.fillColor = cachedCGColor(highlight.marginColor, ColorSpaceDeviceRGB); 230 borderLayer.fillColor = cachedCGColor(highlight.borderColor, ColorSpaceDeviceRGB); 231 paddingLayer.fillColor = cachedCGColor(highlight.paddingColor, ColorSpaceDeviceRGB); 232 contentLayer.fillColor = cachedCGColor(highlight.contentColor, ColorSpaceDeviceRGB); 233 234 layerPathWithHole(marginLayer, marginQuad, borderQuad); 235 layerPathWithHole(borderLayer, borderQuad, paddingQuad); 236 layerPathWithHole(paddingLayer, paddingQuad, contentQuad); 237 layerPath(contentLayer, contentQuad); 238} 239 240- (void)_layoutForRectsHighlight:(const Highlight&)highlight 241{ 242 NSUInteger numLayers = (NSUInteger)highlight.quads.size(); 243 if (!numLayers) { 244 [self _removeAllLayers]; 245 return; 246 } 247 248 [self _createLayers:numLayers]; 249 250 CGColorRef contentColor = cachedCGColor(highlight.contentColor, ColorSpaceDeviceRGB); 251 for (NSUInteger i = 0; i < numLayers; ++i) { 252 CAShapeLayer *layer = [_layers objectAtIndex:i]; 253 layer.fillColor = contentColor; 254 layerPath(layer, highlight.quads[i]); 255 } 256} 257 258- (void)update:(const Highlight&)highlight 259{ 260 if (highlight.type == HighlightTypeNode) 261 [self _layoutForNodeHighlight:highlight]; 262 else if (highlight.type == HighlightTypeRects) 263 [self _layoutForRectsHighlight:highlight]; 264} 265 266@end 267 268#endif 269