import { Background, View, Point, layout, Color } from "./background-common"; import { ios as utilsIos } from "utils/utils"; export * from "./background-common"; interface NativeView extends UIView { hasNonUniformBorder: boolean; topBorderLayer: CAShapeLayer; rightBorderLayer: CAShapeLayer; bottomBorderLayer: CAShapeLayer; leftBorderLayer: CAShapeLayer; } interface Rect { left: number; top: number; right: number; bottom: number; } const clearCGColor = utilsIos.getter(UIColor, UIColor.clearColor).CGColor; export module ios { export function createBackgroundUIColor(view: View, flip?: boolean): UIColor { const background = view.style.backgroundInternal; const nativeView = view.nativeView; if (background.hasUniformBorder()) { if (nativeView.hasNonUniformBorder) { clearNonUniformBorders(nativeView); } const layer = nativeView.layer; const borderColor = background.getUniformBorderColor(); layer.borderColor = !borderColor ? undefined : borderColor.ios.CGColor; layer.borderWidth = background.getUniformBorderWidth(); layer.cornerRadius = background.getUniformBorderRadius(); } else { drawNonUniformBorders(nativeView, background); } // Clip-path should be called after borders are applied. // It will eventually move them to different layer if uniform. if (background.clipPath) { drawClipPath(nativeView, background); } if (!background.image) { return background.color ? background.color.ios : undefined; } else { return getUIColorFromImage(view, nativeView, background, flip); } } } function clearNonUniformBorders(nativeView: NativeView): void { if (nativeView.topBorderLayer) { nativeView.topBorderLayer.removeFromSuperlayer(); } if (nativeView.rightBorderLayer) { nativeView.rightBorderLayer.removeFromSuperlayer(); } if (nativeView.bottomBorderLayer) { nativeView.bottomBorderLayer.removeFromSuperlayer(); } if (nativeView.leftBorderLayer) { nativeView.leftBorderLayer.removeFromSuperlayer(); } } function getUIColorFromImage(view: View, nativeView: UIView, background: Background, flip?: boolean): UIColor { const frame = nativeView.frame; const boundsWidth = view.scaleX ? frame.size.width / view.scaleX : frame.size.width; const boundsHeight = view.scaleY ? frame.size.height / view.scaleY : frame.size.height; if (!boundsWidth || !boundsHeight) { return undefined; } // We have an image for a background let img = background.image.ios; const params = background.getDrawParams(boundsWidth, boundsHeight); if (params.sizeX > 0 && params.sizeY > 0) { const resizeRect = CGRectMake(0, 0, params.sizeX, params.sizeY); UIGraphicsBeginImageContext(resizeRect.size); img.drawInRect(resizeRect); img = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } UIGraphicsBeginImageContextWithOptions(CGSizeFromString(`{${boundsWidth},${boundsHeight}}`), false, 0.0); const context = UIGraphicsGetCurrentContext(); if (background.color && background.color.ios) { CGContextSetFillColorWithColor(context, background.color.ios.CGColor); CGContextFillRect(context, CGRectMake(0, 0, boundsWidth, boundsHeight)); } if (!params.repeatX && !params.repeatY) { img.drawAtPoint(CGPointMake(params.posX, params.posY)); } else { const w = params.repeatX ? boundsWidth : img.size.width; const h = params.repeatY ? boundsHeight : img.size.height; CGContextSetPatternPhase(context, CGSizeMake(params.posX, params.posY)); params.posX = params.repeatX ? 0 : params.posX; params.posY = params.repeatY ? 0 : params.posY; const patternRect = CGRectMake(params.posX, params.posY, w, h); img.drawAsPatternInRect(patternRect); } const bkgImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); if (flip) { const flippedImage = _flipImage(bkgImage); return UIColor.alloc().initWithPatternImage(flippedImage); } return UIColor.alloc().initWithPatternImage(bkgImage); } // Flipping the default coordinate system // https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html function _flipImage(originalImage: UIImage): UIImage { UIGraphicsBeginImageContextWithOptions(originalImage.size, false, 0.0); const context = UIGraphicsGetCurrentContext(); CGContextSaveGState(context); CGContextTranslateCTM(context, 0.0, originalImage.size.height); CGContextScaleCTM(context, 1.0, -1.0); originalImage.drawInRect(CGRectMake(0, 0, originalImage.size.width, originalImage.size.height)) CGContextRestoreGState(context); const flippedImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return flippedImage; } function cssValueToDevicePixels(source: string, total: number): number { let result; source = source.trim(); if (source.indexOf("px") !== -1) { result = parseFloat(source.replace("px", "")); } else if (source.indexOf("%") !== -1 && total > 0) { result = (parseFloat(source.replace("%", "")) / 100) * layout.toDeviceIndependentPixels(total); } else { result = parseFloat(source); } return layout.toDevicePixels(result); } function drawNonUniformBorders(nativeView: NativeView, background: Background) { const layer = nativeView.layer; layer.borderColor = undefined; layer.borderWidth = 0; layer.cornerRadius = 0; nativeView.clipsToBounds = true; const layerBounds = layer.bounds; const layerOrigin = layerBounds.origin; const layerSize = layerBounds.size; const nativeViewLayerBounds = { left: layerOrigin.x, top: layerOrigin.y, bottom: layerSize.height, right: layerSize.width }; const top = background.borderTopWidth; const right = background.borderRightWidth; const bottom = background.borderBottomWidth; const left = background.borderLeftWidth; const lto: Point = { x: nativeViewLayerBounds.left, y: nativeViewLayerBounds.top };// left-top-outside const lti: Point = { x: nativeViewLayerBounds.left + left, y: nativeViewLayerBounds.top + top }; // left-top-inside const rto: Point = { x: nativeViewLayerBounds.right, y: nativeViewLayerBounds.top }; // right-top-outside const rti: Point = { x: nativeViewLayerBounds.right - right, y: nativeViewLayerBounds.top + top }; // right-top-inside const rbo: Point = { x: nativeViewLayerBounds.right, y: nativeViewLayerBounds.bottom }; // right-bottom-outside const rbi: Point = { x: nativeViewLayerBounds.right - right, y: nativeViewLayerBounds.bottom - bottom }; // right-bottom-inside const lbo: Point = { x: nativeViewLayerBounds.left, y: nativeViewLayerBounds.bottom }; // left-bottom-outside const lbi: Point = { x: nativeViewLayerBounds.left + left, y: nativeViewLayerBounds.bottom - bottom }; // left-bottom-inside let hasNonUniformBorder: boolean; const borderTopColor = background.borderTopColor; if (top > 0 && borderTopColor && borderTopColor.ios) { const topBorderPath = CGPathCreateMutable(); CGPathMoveToPoint(topBorderPath, null, lto.x, lto.y); CGPathAddLineToPoint(topBorderPath, null, rto.x, rto.y); CGPathAddLineToPoint(topBorderPath, null, rti.x, rti.y); CGPathAddLineToPoint(topBorderPath, null, lti.x, lti.y); CGPathAddLineToPoint(topBorderPath, null, lto.x, lto.y); const topBorderLayer = CAShapeLayer.layer(); topBorderLayer.fillColor = background.borderTopColor.ios.CGColor; topBorderLayer.path = topBorderPath; layer.addSublayer(topBorderLayer); nativeView.topBorderLayer = topBorderLayer; hasNonUniformBorder = true; } else if (nativeView.topBorderLayer) { nativeView.topBorderLayer.removeFromSuperlayer(); } const borderRightColor = background.borderRightColor; if (right > 0 && borderRightColor && borderRightColor.ios) { const rightBorderPath = CGPathCreateMutable(); CGPathMoveToPoint(rightBorderPath, null, rto.x, rto.y); CGPathAddLineToPoint(rightBorderPath, null, rbo.x, rbo.y); CGPathAddLineToPoint(rightBorderPath, null, rbi.x, rbi.y); CGPathAddLineToPoint(rightBorderPath, null, rti.x, rti.y); CGPathAddLineToPoint(rightBorderPath, null, rto.x, rto.y); const rightBorderLayer = CAShapeLayer.layer(); rightBorderLayer.fillColor = background.borderRightColor.ios.CGColor; rightBorderLayer.path = rightBorderPath; layer.addSublayer(rightBorderLayer); nativeView.rightBorderLayer = rightBorderLayer; hasNonUniformBorder = true; } else if (nativeView.rightBorderLayer) { nativeView.rightBorderLayer.removeFromSuperlayer(); } const borderBottomColor = background.borderBottomColor; if (bottom > 0 && borderBottomColor && borderBottomColor.ios) { const bottomBorderPath = CGPathCreateMutable(); CGPathMoveToPoint(bottomBorderPath, null, rbo.x, rbo.y); CGPathAddLineToPoint(bottomBorderPath, null, lbo.x, lbo.y); CGPathAddLineToPoint(bottomBorderPath, null, lbi.x, lbi.y); CGPathAddLineToPoint(bottomBorderPath, null, rbi.x, rbi.y); CGPathAddLineToPoint(bottomBorderPath, null, rbo.x, rbo.y); const bottomBorderLayer = CAShapeLayer.layer(); bottomBorderLayer.fillColor = background.borderBottomColor.ios.CGColor; bottomBorderLayer.path = bottomBorderPath; layer.addSublayer(bottomBorderLayer); nativeView.bottomBorderLayer = bottomBorderLayer; hasNonUniformBorder = true; } else if (nativeView.bottomBorderLayer) { nativeView.bottomBorderLayer.removeFromSuperlayer(); } const borderLeftColor = background.borderLeftColor; if (left > 0 && borderLeftColor && borderLeftColor.ios) { const leftBorderPath = CGPathCreateMutable(); CGPathMoveToPoint(leftBorderPath, null, lbo.x, lbo.y); CGPathAddLineToPoint(leftBorderPath, null, lto.x, lto.y); CGPathAddLineToPoint(leftBorderPath, null, lti.x, lti.y); CGPathAddLineToPoint(leftBorderPath, null, lbi.x, lbi.y); CGPathAddLineToPoint(leftBorderPath, null, lbo.x, lbo.y); const leftBorderLayer = CAShapeLayer.layer(); leftBorderLayer.fillColor = background.borderLeftColor.ios.CGColor; leftBorderLayer.path = leftBorderPath; layer.addSublayer(leftBorderLayer); nativeView.leftBorderLayer = leftBorderLayer; hasNonUniformBorder = true; } else if (nativeView.leftBorderLayer) { nativeView.leftBorderLayer.removeFromSuperlayer(); } nativeView.hasNonUniformBorder = hasNonUniformBorder; } function drawClipPath(nativeView: UIView, background: Background) { const layer = nativeView.layer; const layerBounds = layer.bounds; const layerOrigin = layerBounds.origin; const layerSize = layerBounds.size; const bounds = { left: layerOrigin.x, top: layerOrigin.y, bottom: layerSize.height, right: layerSize.width }; if (bounds.right === 0 || bounds.bottom === 0) { return; } let path: UIBezierPath; const clipPath = background.clipPath; const functionName = clipPath.substring(0, clipPath.indexOf("(")); const value = clipPath.replace(`${functionName}(`, "").replace(")", ""); switch (functionName) { case "rect": path = rectPath(value, bounds); break; case "inset": path = insetPath(value, bounds); break; case "circle": path = circlePath(value, bounds); break; case "ellipse": path = ellipsePath(value, bounds); break; case "polygon": path = polygonPath(value, bounds); break; } if (path) { const shape = CAShapeLayer.layer(); shape.path = path; layer.mask = shape; nativeView.clipsToBounds = true; const borderWidth = background.getUniformBorderWidth(); const borderColor = background.getUniformBorderColor(); if (borderWidth > 0 && borderColor instanceof Color) { const borderLayer = CAShapeLayer.layer(); borderLayer.path = path; borderLayer.lineWidth = borderWidth * 2; borderLayer.strokeColor = borderColor.ios.CGColor; borderLayer.fillColor = clearCGColor; borderLayer.frame = nativeView.bounds; layer.borderColor = undefined; layer.borderWidth = 0; layer.addSublayer(borderLayer); } } } function rectPath(value: string, bounds: Rect): UIBezierPath { const arr = value.split(/[\s]+/); const top = cssValueToDevicePixels(arr[0], bounds.top); const left = cssValueToDevicePixels(arr[1], bounds.left); const bottom = cssValueToDevicePixels(arr[2], bounds.bottom); const right = cssValueToDevicePixels(arr[3], bounds.right); return UIBezierPath.bezierPathWithRect(CGRectMake(left, top, right - left, bottom - top)).CGPath; } function insetPath(value: string, bounds: Rect): UIBezierPath { const arr = value.split(/[\s]+/); let topString: string; let rightString: string; let bottomString: string; let leftString: string; if (arr.length === 1) { topString = rightString = bottomString = leftString = arr[0]; } else if (arr.length === 2) { topString = bottomString = arr[0]; rightString = leftString = arr[1]; } else if (arr.length === 3) { topString = arr[0]; rightString = leftString = arr[1]; bottomString = arr[2]; } else if (arr.length === 4) { topString = arr[0]; rightString = arr[1]; bottomString = arr[2]; leftString = arr[3]; } const top = cssValueToDevicePixels(topString, bounds.bottom); const right = cssValueToDevicePixels("100%", bounds.right) - cssValueToDevicePixels(rightString, bounds.right); const bottom = cssValueToDevicePixels("100%", bounds.bottom) - cssValueToDevicePixels(bottomString, bounds.bottom); const left = cssValueToDevicePixels(leftString, bounds.right); return UIBezierPath.bezierPathWithRect(CGRectMake(left, top, right - left, bottom - top)).CGPath; } function circlePath(value: string, bounds: Rect): UIBezierPath { const arr = value.split(/[\s]+/); const radius = cssValueToDevicePixels(arr[0], (bounds.right > bounds.bottom ? bounds.bottom : bounds.right) / 2); const y = cssValueToDevicePixels(arr[2], bounds.bottom); const x = cssValueToDevicePixels(arr[3], bounds.right); return UIBezierPath.bezierPathWithArcCenterRadiusStartAngleEndAngleClockwise(CGPointMake(x, y), radius, 0, 360, true).CGPath; } function ellipsePath(value: string, bounds: Rect): UIBezierPath { const arr = value.split(/[\s]+/); const rX = cssValueToDevicePixels(arr[0], bounds.right); const rY = cssValueToDevicePixels(arr[1], bounds.bottom); const cX = cssValueToDevicePixels(arr[3], bounds.right); const cY = cssValueToDevicePixels(arr[4], bounds.bottom); const left = cX - rX; const top = cY - rY; const width = rX * 2; const height = rY * 2; return UIBezierPath.bezierPathWithOvalInRect(CGRectMake(left, top, width, height)).CGPath; } function polygonPath(value: string, bounds: Rect): UIBezierPath { const path = CGPathCreateMutable(); let firstPoint: Point; const arr = value.split(/[,]+/); for (let i = 0; i < arr.length; i++) { const xy = arr[i].trim().split(/[\s]+/); const point: Point = { x: cssValueToDevicePixels(xy[0], bounds.right), y: cssValueToDevicePixels(xy[1], bounds.bottom) }; if (!firstPoint) { firstPoint = point; CGPathMoveToPoint(path, null, point.x, point.y); } CGPathAddLineToPoint(path, null, point.x, point.y); } CGPathAddLineToPoint(path, null, firstPoint.x, firstPoint.y); return path; }