Cappuccino  1.0.0
 All Classes Files Functions Variables Typedefs Macros Groups Pages
CPGradient.j
Go to the documentation of this file.
1 /*
2  * CPGradient.j
3  * AppKit
4  *
5  * Created by Alexander Ljungberg.
6  * Copyright 2012, SlevenBits Ltd.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
27 
31 @implementation CPGradient : CPObject
32 {
33  CGGradient _gradient;
34 }
35 
36 - (id)initWithStartingColor:(CPColor)startingColor endingColor:(CPColor)endingColor
37 {
38  return [self initWithColors:[startingColor, endingColor]];
39 }
40 
41 - (id)initWithColors:(CPArray)someColors
42 {
43  var count = [someColors count];
44 
45  if (count < 2)
46  [CPException raise:CPInvalidArgumentException reason:@"at least 2 colors required"];
47 
48  var distance = 1.0 / (count - 1),
49  locations = [CPMutableArray array],
50  location = 0.0;
51 
52  for (var i = 0; i < count; i++)
53  {
54  [locations addObject:location];
55  location += distance;
56  }
57 
58  return [self initWithColors:someColors atLocations:locations colorSpace:nil];
59 }
60 
61 - (id)initWithColors:(CPArray)someColors atLocations:(CPArray)someLocations colorSpace:(CPColorSpace)aColorSpace
62 {
63  if (self = [super init])
64  {
65  var colorSpace = [aColorSpace CGColorSpace] || CGColorSpaceCreateDeviceRGB,
66  cgColors = [someColors arrayByApplyingBlock:function(color)
67  {
68  return CGColorCreate(colorSpace, [color components])
69  }];
70 
71  _gradient = CGGradientCreateWithColors(colorSpace, cgColors, someLocations);
72  }
73 
74  return self;
75 }
76 
77 - (void)drawInRect:(CGRect)rect angle:(float)angle
78 {
80 
82  CGContextClipToRect(ctx, rect);
83  CGContextAddRect(ctx, rect);
84 
85  [self _drawInRect:rect atAngle:angle];
86 
88 }
89 
93 - (void)_drawInRect:(CGRect)rect atAngle:(float)angle
94 {
96 
97  startPoint,
98  endPoint;
99 
100  // Modulo of negative values doesn't work as expected in JS.
101  angle = ((angle % 360.0) + 360.0) % 360.0;
102 
103  if (angle < 90.0)
104  startPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
105  else if (angle < 180.0)
106  startPoint = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
107  else if (angle < 270.0)
108  startPoint = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect));
109  else
110  startPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect));
111 
112  // A line segment comes out of the starting point at the given angle, with the first colour
113  // at the starting point and the last at the end. To do what drawInRect: is supposed to do
114  // we want the opposite corner of the starting corner to just reach the final colour stop.
115  // So when the angle isn't a right angle, the segment has to extend beyond the edge of the
116  // rectangle just far enough. This is hard to describe without a picture but if we place
117  // another line through the opposite corner at -90 degrees, it'll help form some triangles
118  // from which we can derive this formula:
119  //
120  // length = cos(angle) * (rectWidth + rectHeight * tan(angle))
121  //
122  // This simplifies down to (in the first quadrant) rectWidth * cos(a) + rectHeight * sin(a).
123 
124  var radians = PI * angle / 180.0,
125  length = ABS(CGRectGetWidth(rect) * COS(radians)) + ABS(CGRectGetHeight(rect) * SIN(radians));
126 
127  endPoint = CGPointMake(startPoint.x + length * COS(radians),
128  startPoint.y + length * SIN(radians));
129 
130  [self drawFromPoint:startPoint toPoint:endPoint options:CPGradientDrawsBeforeStartingLocation | CPGradientDrawsAfterEndingLocation];
131 }
132 
133 - (void)drawInBezierPath:(CPBezierPath)aPath angle:(float)anAngle
134 {
136 
138 
139  // Nail down the path which CGContextDrawLinearGradient will cause to be filled.
140  // Note we don't do this by clipping to the path and then calling drawInRect:atAngle:
141  // as this would cut off any antialias of the drawing, plus any active context shadow.
142  CGContextBeginPath(ctx);
143  CGContextAddPath(ctx, aPath._path);
144  CGContextSetLineWidth(ctx, [aPath lineWidth]);
145  CGContextClosePath(ctx);
146 
147  [self _drawInRect:[aPath bounds] atAngle:anAngle];
148 
150 }
151 
152 - (void)drawFromPoint:(NSPoint)startingPoint toPoint:(NSPoint)endingPoint options:(NSGradientDrawingOptions)options
153 {
155 
156  // TODO kCGGradientDrawsBeforeStartLocation and kCGGradientDrawsAfterEndLocation are not actually supported
157  // by CGContextDrawLinearGradient yet.
158  CGContextDrawLinearGradient(ctx, _gradient, startingPoint, endingPoint, options);
159 }
160 
161 @end