Files
NativeScript/tns-core-modules/ui/styling/background.ios.ts
Hristo Hristov 0f14101238 recycling now happens only if nativeView and android properties are not accessed. (#4627)
recycleNativeView filed now accepts: "always" | "never" | "auto". Always will recycle the nativeView no matter if its nativeView or android proprties are accessed. Never will disable recycling. Auto will recycle it only if nativeView and android properties are not accessed.
2017-08-01 15:04:16 +03:00

781 lines
32 KiB
TypeScript

import { Background as BackgroundDefinition } from "./background";
import { View, Point } from "../core/view";
import { Color } from "../../color";
import { ios as utilsIos, isDataURI, isFileOrResourcePath, layout } from "../../utils/utils";
import { fromFileOrResource, fromBase64, fromUrl } from "../../image-source";
import { CSSValue, parse as cssParse } from "../../css-value";
export * from "./background-common";
interface NativeView extends UIView {
hasNonUniformBorder: boolean;
borderLayer: CAShapeLayer;
hasBorderMask: boolean;
borderOriginalMask: CALayer;
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;
const symbolUrl = Symbol("backgroundImageUrl");
export module ios {
export function createBackgroundUIColor(view: View, callback: (uiColor: UIColor) => void, flip?: boolean): void {
const background = view.style.backgroundInternal;
const nativeView = <NativeView>view.nativeViewProtected;
if (nativeView.hasNonUniformBorder) {
clearNonUniformBorders(nativeView);
}
if (background.hasUniformBorderColor() && background.hasBorderRadius()) {
drawUniformColorNonUniformBorders(nativeView, background);
} else if (background.hasUniformBorder()) {
const layer = nativeView.layer;
const borderColor = background.getUniformBorderColor();
layer.borderColor = !borderColor ? undefined : borderColor.ios.CGColor;
layer.borderWidth = layout.toDeviceIndependentPixels(background.getUniformBorderWidth());
const renderSize = view.getActualSize() || { width: 0, height: 0 };
const cornerRadius = layout.toDeviceIndependentPixels(background.getUniformBorderRadius());
layer.cornerRadius = Math.min(Math.min(renderSize.width / 2, renderSize.height / 2), cornerRadius);
} else {
drawNoRadiusNonUniformBorders(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) {
const uiColor = background.color ? background.color.ios : undefined;
callback(uiColor);
} else {
setUIColorFromImage(view, nativeView, callback, flip);
}
}
}
function clearNonUniformBorders(nativeView: NativeView): void {
if (nativeView.borderLayer) {
nativeView.borderLayer.removeFromSuperlayer();
}
if (nativeView.hasBorderMask) {
nativeView.layer.mask = nativeView.borderOriginalMask;
nativeView.hasBorderMask = false;
nativeView.borderOriginalMask = null;
}
if (nativeView.topBorderLayer) {
nativeView.topBorderLayer.removeFromSuperlayer();
}
if (nativeView.rightBorderLayer) {
nativeView.rightBorderLayer.removeFromSuperlayer();
}
if (nativeView.bottomBorderLayer) {
nativeView.bottomBorderLayer.removeFromSuperlayer();
}
if (nativeView.leftBorderLayer) {
nativeView.leftBorderLayer.removeFromSuperlayer();
}
}
const pattern: RegExp = /url\(('|")(.*?)\1\)/;
function setUIColorFromImage(view: View, nativeView: UIView, callback: (uiColor: UIColor) => void, flip?: boolean): void {
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;
}
const style = view.style;
const background = style.backgroundInternal;
let imageUri = background.image;
if (imageUri) {
const match = imageUri.match(pattern);
if (match && match[2]) {
imageUri = match[2];
}
}
let bitmap: UIImage;
if (isDataURI(imageUri)) {
const base64Data = imageUri.split(",")[1];
if (base64Data !== undefined) {
const imageSource = fromBase64(base64Data);
bitmap = imageSource && imageSource.ios
}
} else if (isFileOrResourcePath(imageUri)) {
const imageSource = fromFileOrResource(imageUri);
bitmap = imageSource && imageSource.ios;
} else if (imageUri.indexOf("http") !== -1) {
style[symbolUrl] = imageUri;
fromUrl(imageUri).then((r) => {
if (style && style[symbolUrl] === imageUri) {
uiColorFromImage(r.ios, view, callback, flip);
}
});
}
uiColorFromImage(bitmap, view, callback, flip);
}
interface BackgroundDrawParams {
repeatX: boolean;
repeatY: boolean;
posX: number;
posY: number;
sizeX?: number;
sizeY?: number;
}
function parsePosition(pos: string): { x: CSSValue, y: CSSValue } {
const values = cssParse(pos);
if (values.length === 2) {
return { x: values[0], y: values[1] };
}
if (values.length === 1 && values[0].type === "ident") {
const val = values[0].string.toLocaleLowerCase();
const center = { type: "ident", string: "center" };
// If you only one keyword is specified, the other value is "center"
if (val === "left" || val === "right") {
return { x: values[0], y: center };
} else if (val === "top" || val === "bottom") {
return { x: center, y: values[0] };
} else if (val === "center") {
return { x: center, y: center };
}
}
return null;
};
function getDrawParams(this: void, image: UIImage, background: BackgroundDefinition, width: number, height: number): BackgroundDrawParams {
if (!image) {
return null;
}
const res: BackgroundDrawParams = {
repeatX: true,
repeatY: true,
posX: 0,
posY: 0,
}
// repeat
if (background.repeat) {
switch (background.repeat.toLowerCase()) {
case "no-repeat":
res.repeatX = false;
res.repeatY = false;
break;
case "repeat-x":
res.repeatY = false;
break;
case "repeat-y":
res.repeatX = false;
break;
}
}
const imageSize = image.size;
let imageWidth = imageSize.width;
let imageHeight = imageSize.height;
// size
const size = background.size;
if (size) {
const values = cssParse(size);
if (values.length === 2) {
const vx = values[0];
const vy = values[1];
if (vx.unit === "%" && vy.unit === "%") {
imageWidth = width * vx.value / 100;
imageHeight = height * vy.value / 100;
res.sizeX = imageWidth;
res.sizeY = imageHeight;
} else if (vx.type === "number" && vy.type === "number" &&
((vx.unit === "px" && vy.unit === "px") || (vx.unit === "" && vy.unit === ""))) {
imageWidth = vx.value;
imageHeight = vy.value;
res.sizeX = imageWidth;
res.sizeY = imageHeight;
}
}
else if (values.length === 1 && values[0].type === "ident") {
let scale = 0;
if (values[0].string === "cover") {
scale = Math.max(width / imageWidth, height / imageHeight);
} else if (values[0].string === "contain") {
scale = Math.min(width / imageWidth, height / imageHeight);
}
if (scale > 0) {
imageWidth *= scale;
imageHeight *= scale;
res.sizeX = imageWidth;
res.sizeY = imageHeight;
}
}
}
// position
const position = background.position;
if (position) {
const v = parsePosition(position);
if (v) {
const spaceX = width - imageWidth;
const spaceY = height - imageHeight;
if (v.x.unit === "%" && v.y.unit === "%") {
res.posX = spaceX * v.x.value / 100;
res.posY = spaceY * v.y.value / 100;
} else if (v.x.type === "number" && v.y.type === "number" &&
((v.x.unit === "px" && v.y.unit === "px") || (v.x.unit === "" && v.y.unit === ""))) {
res.posX = v.x.value;
res.posY = v.y.value;
} else if (v.x.type === "ident" && v.y.type === "ident") {
if (v.x.string.toLowerCase() === "center") {
res.posX = spaceX / 2;
} else if (v.x.string.toLowerCase() === "right") {
res.posX = spaceX;
}
if (v.y.string.toLowerCase() === "center") {
res.posY = spaceY / 2;
} else if (v.y.string.toLowerCase() === "bottom") {
res.posY = spaceY;
}
}
}
}
return res;
}
function uiColorFromImage(img: UIImage, view: View, callback: (uiColor: UIColor) => void, flip?: boolean): void {
const background = view.style.backgroundInternal;
if (!img) {
callback(background.color && background.color.ios);
return;
}
const nativeView = view.nativeViewProtected as UIView;
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;
const params = getDrawParams(img, background, 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);
callback(UIColor.alloc().initWithPatternImage(flippedImage));
} else {
callback(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 cssValueToDeviceIndependentPixels(source: string, total: number): number {
source = source.trim();
if (source.indexOf("px") !== -1) {
return layout.toDeviceIndependentPixels(parseFloat(source.replace("px", "")));
} else if (source.indexOf("%") !== -1 && total > 0) {
return (parseFloat(source.replace("%", "")) / 100) * total;
} else {
return parseFloat(source);
}
}
function drawUniformColorNonUniformBorders(nativeView: NativeView, background: BackgroundDefinition) {
const layer = nativeView.layer;
layer.backgroundColor = undefined;
layer.borderColor = undefined;
layer.borderWidth = 0;
layer.cornerRadius = 0;
const { width, height } = layer.bounds.size;
const { x, y } = layer.bounds.origin;
const left = x;
const top = y;
const right = x + width;
const bottom = y + height;
const { min, max } = Math;
const borderTopWidth = max(0, layout.toDeviceIndependentPixels(background.borderTopWidth));
const borderRightWidth = max(0, layout.toDeviceIndependentPixels(background.borderRightWidth));
const borderBottomWidth = max(0, layout.toDeviceIndependentPixels(background.borderBottomWidth));
const borderLeftWidth = max(0, layout.toDeviceIndependentPixels(background.borderLeftWidth));
const borderVWidth = borderTopWidth + borderBottomWidth;
const borderHWidth = borderLeftWidth + borderRightWidth;
const cappedBorderTopWidth = borderTopWidth && borderTopWidth * min(1, height / borderVWidth)
const cappedBorderRightWidth = borderRightWidth && borderRightWidth * min(1, width / borderHWidth);
const cappedBorderBottomWidth = borderBottomWidth && borderBottomWidth * min(1, height / borderVWidth);
const cappedBorderLeftWidth = borderLeftWidth && borderLeftWidth * min(1, width / borderHWidth);
const outerTopLeftRadius = layout.toDeviceIndependentPixels(background.borderTopLeftRadius);
const outerTopRightRadius = layout.toDeviceIndependentPixels(background.borderTopRightRadius);
const outerBottomRightRadius = layout.toDeviceIndependentPixels(background.borderBottomRightRadius);
const outerBottomLeftRadius = layout.toDeviceIndependentPixels(background.borderBottomLeftRadius);
const topRadii = outerTopLeftRadius + outerTopRightRadius;
const rightRadii = outerTopRightRadius + outerBottomRightRadius;
const bottomRadii = outerBottomRightRadius + outerBottomLeftRadius;
const leftRadii = outerBottomLeftRadius + outerTopLeftRadius;
function capRadius(a: number, b: number, c: number): number {
return a && Math.min(a, Math.min(b, c));
}
const cappedOuterTopLeftRadius = capRadius(outerTopLeftRadius, outerTopLeftRadius / topRadii * width, outerTopLeftRadius / leftRadii * height);
const cappedOuterTopRightRadius = capRadius(outerTopRightRadius, outerTopRightRadius / topRadii * width, outerTopRightRadius / rightRadii * height);
const cappedOuterBottomRightRadius = capRadius(outerBottomRightRadius, outerBottomRightRadius / bottomRadii * width, outerBottomRightRadius / rightRadii * height);
const cappedOuterBottomLeftRadius = capRadius(outerBottomLeftRadius, outerBottomLeftRadius / bottomRadii * width, outerBottomLeftRadius / leftRadii * height);
// Outer contour
const clipPath = CGPathCreateMutable();
CGPathMoveToPoint(clipPath, null, left + cappedOuterTopLeftRadius, top);
CGPathAddArcToPoint(clipPath, null, right, top, right, top + cappedOuterTopRightRadius, cappedOuterTopRightRadius);
CGPathAddArcToPoint(clipPath, null, right, bottom, right - cappedOuterBottomRightRadius, bottom, cappedOuterBottomRightRadius);
CGPathAddArcToPoint(clipPath, null, left, bottom, left, bottom - cappedOuterBottomLeftRadius, cappedOuterBottomLeftRadius);
CGPathAddArcToPoint(clipPath, null, left, top, left + cappedOuterTopLeftRadius, top, cappedOuterTopLeftRadius);
CGPathCloseSubpath(clipPath);
nativeView.borderOriginalMask = layer.mask;
const clipShapeLayer = CAShapeLayer.layer();
clipShapeLayer.path = clipPath;
layer.mask = clipShapeLayer;
nativeView.hasBorderMask = true;
if (cappedBorderLeftWidth > 0 || cappedBorderTopWidth > 0 || cappedBorderRightWidth > 0 || cappedBorderBottomWidth > 0) {
const borderPath = CGPathCreateMutable();
CGPathAddRect(borderPath, null, CGRectMake(left, top, width, height));
// Inner contour
if (cappedBorderTopWidth > 0 || cappedBorderLeftWidth > 0) {
CGPathMoveToPoint(borderPath, null, left + cappedOuterTopLeftRadius, top + cappedBorderTopWidth);
} else {
CGPathMoveToPoint(borderPath, null, left, top);
}
if (cappedBorderTopWidth > 0 || cappedBorderRightWidth > 0) {
const innerTopRightWRadius = max(0, cappedOuterTopRightRadius - cappedBorderRightWidth);
const innerTopRightHRadius = max(0, cappedOuterTopRightRadius - cappedBorderTopWidth);
const innerTopRightMaxRadius = max(innerTopRightWRadius, innerTopRightHRadius);
const innerTopRightTransform: any = CGAffineTransformMake(
innerTopRightMaxRadius && innerTopRightWRadius / innerTopRightMaxRadius, 0,
0, innerTopRightMaxRadius && innerTopRightHRadius / innerTopRightMaxRadius,
right - cappedBorderRightWidth - innerTopRightWRadius, top + cappedBorderTopWidth + innerTopRightHRadius
);
CGPathAddArc(borderPath, innerTopRightTransform, 0, 0, innerTopRightMaxRadius, Math.PI * 3 / 2, 0, false);
} else {
CGPathMoveToPoint(borderPath, null, right, top);
}
if (cappedBorderBottomWidth > 0 || cappedBorderRightWidth > 0) {
const innerBottomRightWRadius = max(0, cappedOuterBottomRightRadius - cappedBorderRightWidth);
const innerBottomRightHRadius = max(0, cappedOuterBottomRightRadius - cappedBorderBottomWidth);
const innerBottomRightMaxRadius = max(innerBottomRightWRadius, innerBottomRightHRadius);
const innerBottomRightTransform: any = CGAffineTransformMake(
innerBottomRightMaxRadius && innerBottomRightWRadius / innerBottomRightMaxRadius, 0,
0, innerBottomRightMaxRadius && innerBottomRightHRadius / innerBottomRightMaxRadius,
right - cappedBorderRightWidth - innerBottomRightWRadius, bottom - cappedBorderBottomWidth - innerBottomRightHRadius
);
CGPathAddArc(borderPath, innerBottomRightTransform, 0, 0, innerBottomRightMaxRadius, 0, Math.PI / 2, false);
} else {
CGPathAddLineToPoint(borderPath, null, right, bottom);
}
if (cappedBorderBottomWidth > 0 || cappedBorderLeftWidth > 0) {
const innerBottomLeftWRadius = max(0, cappedOuterBottomLeftRadius - cappedBorderLeftWidth);
const innerBottomLeftHRadius = max(0, cappedOuterBottomLeftRadius - cappedBorderBottomWidth);
const innerBottomLeftMaxRadius = max(innerBottomLeftWRadius, innerBottomLeftHRadius);
const innerBottomLeftTransform: any = CGAffineTransformMake(
innerBottomLeftMaxRadius && innerBottomLeftWRadius / innerBottomLeftMaxRadius, 0,
0, innerBottomLeftMaxRadius && innerBottomLeftHRadius / innerBottomLeftMaxRadius,
left + cappedBorderLeftWidth + innerBottomLeftWRadius, bottom - cappedBorderBottomWidth - innerBottomLeftHRadius
);
CGPathAddArc(borderPath, innerBottomLeftTransform, 0, 0, innerBottomLeftMaxRadius, Math.PI / 2, Math.PI, false);
} else {
CGPathAddLineToPoint(borderPath, null, left, bottom);
}
if (cappedBorderTopWidth > 0 || cappedBorderLeftWidth > 0) {
const innerTopLeftWRadius = max(0, cappedOuterTopLeftRadius - cappedBorderLeftWidth);
const innerTopLeftHRadius = max(0, cappedOuterTopLeftRadius - cappedBorderTopWidth);
const innerTopLeftMaxRadius = max(innerTopLeftWRadius, innerTopLeftHRadius);
const innerTopLeftTransform: any = CGAffineTransformMake(
innerTopLeftMaxRadius && innerTopLeftWRadius / innerTopLeftMaxRadius, 0,
0, innerTopLeftMaxRadius && innerTopLeftHRadius / innerTopLeftMaxRadius,
left + cappedBorderLeftWidth + innerTopLeftWRadius, top + cappedBorderTopWidth + innerTopLeftHRadius
);
CGPathAddArc(borderPath, innerTopLeftTransform, 0, 0, innerTopLeftMaxRadius, Math.PI, Math.PI * 3 / 2, false);
} else {
CGPathAddLineToPoint(borderPath, null, left, top);
}
CGPathCloseSubpath(borderPath);
const borderLayer = CAShapeLayer.layer();
borderLayer.fillColor = background.borderTopColor && background.borderTopColor.ios.CGColor || UIColor.blackColor.CGColor;
borderLayer.fillRule = kCAFillRuleEvenOdd;
borderLayer.path = borderPath;
layer.addSublayer(borderLayer);
nativeView.borderLayer = borderLayer;
}
nativeView.hasNonUniformBorder = true;
}
function drawNoRadiusNonUniformBorders(nativeView: NativeView, background: BackgroundDefinition) {
const layer = nativeView.layer;
layer.borderColor = undefined;
layer.borderWidth = 0;
layer.cornerRadius = 0;
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 = layout.toDeviceIndependentPixels(background.borderTopWidth);
const right = layout.toDeviceIndependentPixels(background.borderRightWidth);
const bottom = layout.toDeviceIndependentPixels(background.borderBottomWidth);
const left = layout.toDeviceIndependentPixels(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;
}
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;
}
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;
}
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;
}
nativeView.hasNonUniformBorder = hasNonUniformBorder;
}
function drawClipPath(nativeView: UIView, background: BackgroundDefinition) {
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 = cssValueToDeviceIndependentPixels(arr[0], bounds.top);
const right = cssValueToDeviceIndependentPixels(arr[1], bounds.right);
const bottom = cssValueToDeviceIndependentPixels(arr[2], bounds.bottom);
const left = cssValueToDeviceIndependentPixels(arr[3], bounds.left);
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 = cssValueToDeviceIndependentPixels(topString, bounds.bottom);
const right = cssValueToDeviceIndependentPixels("100%", bounds.right) - cssValueToDeviceIndependentPixels(rightString, bounds.right);
const bottom = cssValueToDeviceIndependentPixels("100%", bounds.bottom) - cssValueToDeviceIndependentPixels(bottomString, bounds.bottom);
const left = cssValueToDeviceIndependentPixels(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 = cssValueToDeviceIndependentPixels(arr[0], (bounds.right > bounds.bottom ? bounds.bottom : bounds.right) / 2);
const y = cssValueToDeviceIndependentPixels(arr[2], bounds.bottom);
const x = cssValueToDeviceIndependentPixels(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 = cssValueToDeviceIndependentPixels(arr[0], bounds.right);
const rY = cssValueToDeviceIndependentPixels(arr[1], bounds.bottom);
const cX = cssValueToDeviceIndependentPixels(arr[3], bounds.right);
const cY = cssValueToDeviceIndependentPixels(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: cssValueToDeviceIndependentPixels(xy[0], bounds.right),
y: cssValueToDeviceIndependentPixels(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;
}