blob: 4f5a82d93b79e3d41f535a150078262830eac495 [file] [log] [blame] [edit]
// Copyright 2017-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "MDCRectangleShapeGenerator.h"
#import "MDCCornerTreatment.h"
#import "MDCEdgeTreatment.h"
#import "MDCPathGenerator.h"
#import "MaterialMath.h"
static inline CGFloat CGPointDistanceToPoint(CGPoint a, CGPoint b) {
return MDCHypot(a.x - b.x, a.y - b.y);
}
// Edges in clockwise order
typedef enum : NSUInteger {
MDCShapeEdgeTop = 0,
MDCShapeEdgeRight,
MDCShapeEdgeBottom,
MDCShapeEdgeLeft,
} MDCShapeEdgePosition;
// Corners in clockwise order
typedef enum : NSUInteger {
MDCShapeCornerTopLeft = 0,
MDCShapeCornerTopRight,
MDCShapeCornerBottomRight,
MDCShapeCornerBottomLeft,
} MDCShapeCornerPosition;
@implementation MDCRectangleShapeGenerator
- (instancetype)init {
if (self = [super init]) {
[self setEdges:[[MDCEdgeTreatment alloc] init]];
[self setCorners:[[MDCCornerTreatment alloc] init]];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
MDCRectangleShapeGenerator *copy = [[[self class] alloc] init];
copy.topLeftCorner = [copy.topLeftCorner copyWithZone:zone];
copy.topRightCorner = [copy.topRightCorner copyWithZone:zone];
copy.bottomRightCorner = [copy.bottomRightCorner copyWithZone:zone];
copy.bottomLeftCorner = [copy.bottomLeftCorner copyWithZone:zone];
copy.topLeftCornerOffset = copy.topLeftCornerOffset;
copy.topRightCornerOffset = copy.topRightCornerOffset;
copy.bottomRightCornerOffset = copy.bottomRightCornerOffset;
copy.bottomLeftCornerOffset = copy.bottomLeftCornerOffset;
copy.topEdge = [copy.topEdge copyWithZone:zone];
copy.rightEdge = [copy.rightEdge copyWithZone:zone];
copy.bottomEdge = [copy.bottomEdge copyWithZone:zone];
copy.leftEdge = [copy.leftEdge copyWithZone:zone];
return copy;
}
- (void)setCorners:(MDCCornerTreatment *)cornerShape {
self.topLeftCorner = [cornerShape copy];
self.topRightCorner = [cornerShape copy];
self.bottomRightCorner = [cornerShape copy];
self.bottomLeftCorner = [cornerShape copy];
}
- (void)setEdges:(MDCEdgeTreatment *)edgeShape {
self.topEdge = [edgeShape copy];
self.rightEdge = [edgeShape copy];
self.bottomEdge = [edgeShape copy];
self.leftEdge = [edgeShape copy];
}
- (MDCCornerTreatment *)cornerTreatmentForPosition:(MDCShapeCornerPosition)position {
switch (position) {
case MDCShapeCornerTopLeft:
return self.topLeftCorner;
case MDCShapeCornerTopRight:
return self.topRightCorner;
case MDCShapeCornerBottomLeft:
return self.bottomLeftCorner;
case MDCShapeCornerBottomRight:
return self.bottomRightCorner;
}
}
- (CGPoint)cornerOffsetForPosition:(MDCShapeCornerPosition)position {
switch (position) {
case MDCShapeCornerTopLeft:
return self.topLeftCornerOffset;
case MDCShapeCornerTopRight:
return self.topRightCornerOffset;
case MDCShapeCornerBottomLeft:
return self.bottomLeftCornerOffset;
case MDCShapeCornerBottomRight:
return self.bottomRightCornerOffset;
}
}
- (MDCEdgeTreatment *)edgeTreatmentForPosition:(MDCShapeEdgePosition)position {
switch (position) {
case MDCShapeEdgeTop:
return self.topEdge;
case MDCShapeEdgeLeft:
return self.leftEdge;
case MDCShapeEdgeRight:
return self.rightEdge;
case MDCShapeEdgeBottom:
return self.bottomEdge;
}
}
- (CGPathRef)pathForSize:(CGSize)size {
CGMutablePathRef path = CGPathCreateMutable();
MDCPathGenerator *cornerPaths[4];
CGAffineTransform cornerTransforms[4];
CGAffineTransform edgeTransforms[4];
CGFloat edgeAngles[4];
CGFloat edgeLengths[4];
// Start by getting the path of each corner and calculating edge angles.
for (NSInteger i = 0; i < 4; i++) {
MDCCornerTreatment *cornerShape = [self cornerTreatmentForPosition:i];
CGFloat cornerAngle = [self angleOfCorner:i forViewSize:size];
if (cornerShape.valueType == MDCCornerTreatmentValueTypeAbsolute) {
cornerPaths[i] = [cornerShape pathGeneratorForCornerWithAngle:cornerAngle];
} else if (cornerShape.valueType == MDCCornerTreatmentValueTypePercentage) {
cornerPaths[i] = [cornerShape pathGeneratorForCornerWithAngle:cornerAngle forViewSize:size];
}
edgeAngles[i] = [self angleOfEdge:i forViewSize:size];
}
// Create transformation matrices for each corner and edge
for (NSInteger i = 0; i < 4; i++) {
CGPoint cornerCoords = [self cornerCoordsForPosition:i forViewSize:size];
CGAffineTransform cornerTransform =
CGAffineTransformMakeTranslation(cornerCoords.x, cornerCoords.y);
CGFloat prevEdgeAngle = edgeAngles[(i + 4 - 1) % 4];
// We add 90 degrees (M_PI_2) here because the corner starts rotated from the edge.
cornerTransform = CGAffineTransformRotate(cornerTransform, prevEdgeAngle + (CGFloat)M_PI_2);
cornerTransforms[i] = cornerTransform;
CGPoint edgeStartPoint =
CGPointApplyAffineTransform(cornerPaths[i].endPoint, cornerTransforms[i]);
CGAffineTransform edgeTransform =
CGAffineTransformMakeTranslation(edgeStartPoint.x, edgeStartPoint.y);
CGFloat edgeAngle = edgeAngles[i];
edgeTransform = CGAffineTransformRotate(edgeTransform, edgeAngle);
edgeTransforms[i] = edgeTransform;
}
// Calculate the length of each edge using the transformed corner paths.
for (NSInteger i = 0; i < 4; i++) {
NSInteger next = (i + 1) % 4;
CGPoint edgeStartPoint =
CGPointApplyAffineTransform(cornerPaths[i].endPoint, cornerTransforms[i]);
CGPoint edgeEndPoint =
CGPointApplyAffineTransform(cornerPaths[next].startPoint, cornerTransforms[next]);
edgeLengths[i] = CGPointDistanceToPoint(edgeStartPoint, edgeEndPoint);
}
// Draw the first corner manually because we have to MoveToPoint to start the path.
CGPathMoveToPoint(path, &cornerTransforms[0], cornerPaths[0].startPoint.x,
cornerPaths[0].startPoint.y);
[cornerPaths[0] appendToCGPath:path transform:&cornerTransforms[0]];
// Draw the remaining three corners joined by edges.
for (NSInteger i = 1; i < 4; i++) {
// draw the edge from the previous point to the current point
MDCEdgeTreatment *edge = [self edgeTreatmentForPosition:(i - 1)];
MDCPathGenerator *edgePath = [edge pathGeneratorForEdgeWithLength:edgeLengths[i - 1]];
[edgePath appendToCGPath:path transform:&edgeTransforms[i - 1]];
MDCPathGenerator *cornerPath = cornerPaths[i];
[cornerPath appendToCGPath:path transform:&cornerTransforms[i]];
}
// Draw final edge back to first point.
MDCEdgeTreatment *edge = [self edgeTreatmentForPosition:3];
MDCPathGenerator *edgePath = [edge pathGeneratorForEdgeWithLength:edgeLengths[3]];
[edgePath appendToCGPath:path transform:&edgeTransforms[3]];
CGPathCloseSubpath(path);
return CFAutorelease(path);
}
- (CGFloat)angleOfCorner:(MDCShapeCornerPosition)cornerPosition forViewSize:(CGSize)size {
CGPoint prevCornerCoord = [self cornerCoordsForPosition:(cornerPosition - 1 + 4) % 4
forViewSize:size];
CGPoint nextCornerCoord = [self cornerCoordsForPosition:(cornerPosition + 1) % 4
forViewSize:size];
CGPoint cornerCoord = [self cornerCoordsForPosition:cornerPosition forViewSize:size];
CGPoint prevVector =
CGPointMake(prevCornerCoord.x - cornerCoord.x, prevCornerCoord.y - cornerCoord.y);
CGPoint nextVector =
CGPointMake(nextCornerCoord.x - cornerCoord.x, nextCornerCoord.y - cornerCoord.y);
CGFloat prevAngle = MDCAtan2(prevVector.y, prevVector.x);
CGFloat nextAngle = MDCAtan2(nextVector.y, nextVector.x);
CGFloat angle = prevAngle - nextAngle;
if (angle < 0)
angle += (CGFloat)(2 * M_PI);
return angle;
}
- (CGFloat)angleOfEdge:(MDCShapeEdgePosition)edgePosition forViewSize:(CGSize)size {
MDCShapeCornerPosition startCornerPosition = (MDCShapeCornerPosition)edgePosition;
MDCShapeCornerPosition endCornerPosition = (startCornerPosition + 1) % 4;
CGPoint startCornerCoord = [self cornerCoordsForPosition:startCornerPosition forViewSize:size];
CGPoint endCornerCoord = [self cornerCoordsForPosition:endCornerPosition forViewSize:size];
CGPoint edgeVector =
CGPointMake(endCornerCoord.x - startCornerCoord.x, endCornerCoord.y - startCornerCoord.y);
return MDCAtan2(edgeVector.y, edgeVector.x);
}
- (CGPoint)cornerCoordsForPosition:(MDCShapeCornerPosition)cornerPosition
forViewSize:(CGSize)viewSize {
CGPoint offset = [self cornerOffsetForPosition:cornerPosition];
CGPoint translation;
switch (cornerPosition) {
case MDCShapeCornerTopLeft:
translation = CGPointMake(0, 0);
break;
case MDCShapeCornerTopRight:
translation = CGPointMake(viewSize.width, 0);
break;
case MDCShapeCornerBottomLeft:
translation = CGPointMake(0, viewSize.height);
break;
case MDCShapeCornerBottomRight:
translation = CGPointMake(viewSize.width, viewSize.height);
break;
}
return CGPointMake(offset.x + translation.x, offset.y + translation.y);
}
@end