Skip to content

Commit aba3093

Browse files
authored
fix(ios): non-uniform border angle (#10437)
1 parent 07d2129 commit aba3093

File tree

2 files changed

+71
-100
lines changed

2 files changed

+71
-100
lines changed

packages/core/ui/styling/background.ios.ts

Lines changed: 71 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { LinearGradient } from './linear-gradient';
66
import { Color } from '../../color';
77
import { Screen } from '../../platform';
88
import { isDataURI, isFileOrResourcePath, layout } from '../../utils';
9-
import { extendPointsToTargetY } from '../../utils/number-utils';
109
import { ios as iosViewUtils, NativeScriptUIView } from '../utils';
1110
import { ImageSource } from '../../image-source';
1211
import { CSSValue, parse as cssParse } from '../../css-value';
@@ -301,26 +300,22 @@ export namespace ios {
301300

302301
export function generateNonUniformBorderInnerClipRoundedPath(view: View, bounds: CGRect): any {
303302
const background = view.style.backgroundInternal;
304-
const nativeView = <NativeScriptUIView>view.nativeViewProtected;
305303

306304
const cappedOuterRadii = calculateNonUniformBorderCappedRadii(bounds, background);
307305
return generateNonUniformBorderInnerClipPath(bounds, background, cappedOuterRadii);
308306
}
309307

310308
export function generateNonUniformBorderOuterClipRoundedPath(view: View, bounds: CGRect): any {
311309
const background = view.style.backgroundInternal;
312-
const nativeView = <NativeScriptUIView>view.nativeViewProtected;
313310

314311
const cappedOuterRadii = calculateNonUniformBorderCappedRadii(bounds, background);
315312
return generateNonUniformBorderOuterClipPath(bounds, cappedOuterRadii);
316313
}
317314

318315
export function generateNonUniformMultiColorBorderRoundedPaths(view: View, bounds: CGRect): Array<any> {
319316
const background = view.style.backgroundInternal;
320-
const nativeView = <NativeScriptUIView>view.nativeViewProtected;
321317

322-
const cappedOuterRadii = calculateNonUniformBorderCappedRadii(bounds, background);
323-
return generateNonUniformMultiColorBorderPaths(bounds, background, cappedOuterRadii);
318+
return generateNonUniformMultiColorBorderPaths(bounds, background);
324319
}
325320
}
326321

@@ -779,7 +774,7 @@ function drawNonUniformBorders(nativeView: NativeScriptUIView, background: Backg
779774
borderLeftLayer = nativeView.borderLayer.sublayers[3];
780775
}
781776

782-
const paths = generateNonUniformMultiColorBorderPaths(layerBounds, background, cappedOuterRadii);
777+
const paths = generateNonUniformMultiColorBorderPaths(layerBounds, background);
783778

784779
borderTopLayer.fillColor = background.borderTopColor?.ios?.CGColor || UIColor.blackColor.CGColor;
785780
borderTopLayer.path = paths[0];
@@ -918,16 +913,55 @@ function generateNonUniformBorderInnerClipPath(bounds: CGRect, background: Backg
918913
}
919914

920915
/**
921-
* Generates paths for visualizing borders with different color per side.
922-
* This is achieved by extending all borders enough to consume entire view size and
923-
* use an inner path along with even-odd fill rule to render borders according to their corresponding width.
916+
* Calculates the needed widths for creating triangular shapes for each border.
917+
* To achieve this, all border widths are scaled according to view bounds.
924918
*
925919
* @param bounds
926920
* @param background
927-
* @param cappedOuterRadii
928921
* @returns
929922
*/
930-
function generateNonUniformMultiColorBorderPaths(bounds: CGRect, background: BackgroundDefinition, cappedOuterRadii: CappedOuterRadii): Array<any> {
923+
function getBorderTriangleWidths(bounds: CGRect, background: BackgroundDefinition): Position {
924+
const width: number = bounds.origin.x + bounds.size.width;
925+
const height: number = bounds.origin.y + bounds.size.height;
926+
927+
const borderTopWidth: number = Math.max(0, layout.toDeviceIndependentPixels(background.borderTopWidth));
928+
const borderRightWidth: number = Math.max(0, layout.toDeviceIndependentPixels(background.borderRightWidth));
929+
const borderBottomWidth: number = Math.max(0, layout.toDeviceIndependentPixels(background.borderBottomWidth));
930+
const borderLeftWidth: number = Math.max(0, layout.toDeviceIndependentPixels(background.borderLeftWidth));
931+
932+
const verticalBorderWidth: number = borderTopWidth + borderBottomWidth;
933+
const horizontalBorderWidth: number = borderLeftWidth + borderRightWidth;
934+
935+
let verticalBorderMultiplier = verticalBorderWidth > 0 ? height / verticalBorderWidth : 0;
936+
let horizontalBorderMultiplier = horizontalBorderWidth > 0 ? width / horizontalBorderWidth : 0;
937+
938+
// Both directions should consider each other in order to scale widths properly, as a view might have different width and height
939+
if (verticalBorderMultiplier > 0 && verticalBorderMultiplier < horizontalBorderMultiplier) {
940+
horizontalBorderMultiplier -= horizontalBorderMultiplier - verticalBorderMultiplier;
941+
}
942+
943+
if (horizontalBorderMultiplier > 0 && horizontalBorderMultiplier < verticalBorderMultiplier) {
944+
verticalBorderMultiplier -= verticalBorderMultiplier - horizontalBorderMultiplier;
945+
}
946+
947+
return {
948+
top: borderTopWidth * verticalBorderMultiplier,
949+
right: borderRightWidth * horizontalBorderMultiplier,
950+
bottom: borderBottomWidth * verticalBorderMultiplier,
951+
left: borderLeftWidth * horizontalBorderMultiplier,
952+
};
953+
}
954+
955+
/**
956+
* Generates paths for visualizing borders with different colors per side.
957+
* This is achieved by extending all borders enough to consume entire view size,
958+
* then using an even-odd inner mask to clip and eventually render borders according to their corresponding width.
959+
*
960+
* @param bounds
961+
* @param background
962+
* @returns
963+
*/
964+
function generateNonUniformMultiColorBorderPaths(bounds: CGRect, background: BackgroundDefinition): Array<any> {
931965
const { width, height } = bounds.size;
932966
const { x, y } = bounds.origin;
933967

@@ -938,139 +972,97 @@ function generateNonUniformMultiColorBorderPaths(bounds: CGRect, background: Bac
938972
right: x + width,
939973
};
940974

941-
const topWidth: number = layout.toDeviceIndependentPixels(background.borderTopWidth);
942-
const rightWidth: number = layout.toDeviceIndependentPixels(background.borderRightWidth);
943-
const bottomWidth: number = layout.toDeviceIndependentPixels(background.borderBottomWidth);
944-
const leftWidth: number = layout.toDeviceIndependentPixels(background.borderLeftWidth);
945-
946-
// These values have 1 as fallback in order to handler borders with zero values
947-
const safeTopWidth: number = Math.max(topWidth, 1);
948-
const safeRightWidth: number = Math.max(rightWidth, 1);
949-
const safeBottomWidth: number = Math.max(bottomWidth, 1);
950-
const safeLeftWidth: number = Math.max(leftWidth, 1);
951-
975+
const borderWidths: Position = getBorderTriangleWidths(bounds, background);
952976
const paths = new Array(4);
953977

954978
const lto: Point = {
955979
x: position.left,
956980
y: position.top,
957981
}; // left-top-outside
958982
const lti: Point = {
959-
x: position.left + safeLeftWidth,
960-
y: position.top + safeTopWidth,
983+
x: position.left + borderWidths.left,
984+
y: position.top + borderWidths.top,
961985
}; // left-top-inside
962986

963987
const rto: Point = {
964988
x: position.right,
965989
y: position.top,
966990
}; // right-top-outside
967991
const rti: Point = {
968-
x: position.right - safeRightWidth,
969-
y: position.top + safeTopWidth,
992+
x: position.right - borderWidths.right,
993+
y: position.top + borderWidths.top,
970994
}; // right-top-inside
971995

972996
const rbo: Point = {
973997
x: position.right,
974998
y: position.bottom,
975999
}; // right-bottom-outside
9761000
const rbi: Point = {
977-
x: position.right - safeRightWidth,
978-
y: position.bottom - safeBottomWidth,
1001+
x: position.right - borderWidths.right,
1002+
y: position.bottom - borderWidths.bottom,
9791003
}; // right-bottom-inside
9801004

9811005
const lbo: Point = {
9821006
x: position.left,
9831007
y: position.bottom,
9841008
}; // left-bottom-outside
9851009
const lbi: Point = {
986-
x: position.left + safeLeftWidth,
987-
y: position.bottom - safeBottomWidth,
1010+
x: position.left + borderWidths.left,
1011+
y: position.bottom - borderWidths.bottom,
9881012
}; // left-bottom-inside
9891013

990-
const centerX: number = position.right / 2;
991-
const centerY: number = position.bottom / 2;
992-
993-
// These values help calculate the size that each border shape should consume
994-
const averageHorizontalBorderWidth: number = Math.max((leftWidth + rightWidth) / 2, 1);
995-
const averageVerticalBorderWidth: number = Math.max((topWidth + bottomWidth) / 2, 1);
996-
const viewRatioMultiplier: number = width > 0 && height > 0 ? width / height : 1;
997-
9981014
const borderTopColor = background.borderTopColor;
9991015
const borderRightColor = background.borderRightColor;
10001016
const borderBottomColor = background.borderBottomColor;
10011017
const borderLeftColor = background.borderLeftColor;
10021018

1003-
let borderTopY: number = centerY * (safeTopWidth / averageHorizontalBorderWidth) * viewRatioMultiplier;
1004-
let borderRightX: number = position.right - (centerX * (safeRightWidth / averageVerticalBorderWidth)) / viewRatioMultiplier;
1005-
let borderBottomY: number = position.bottom - centerY * (safeBottomWidth / averageHorizontalBorderWidth) * viewRatioMultiplier;
1006-
let borderLeftX: number = (centerX * (safeLeftWidth / averageVerticalBorderWidth)) / viewRatioMultiplier;
1007-
1008-
// Adjust border triangle width in case of borders colliding between each other or borders being less than 4
1009-
const hasHorizontalIntersection: boolean = borderLeftX > borderRightX;
1010-
const hasVerticalIntersection: boolean = borderTopY > borderBottomY;
1011-
if (hasVerticalIntersection) {
1012-
borderTopY = extendPointsToTargetY(lto.y, lto.x, lti.y, lti.x, borderLeftX);
1013-
borderBottomY = extendPointsToTargetY(lbo.y, lbo.x, lbi.y, lbi.x, borderLeftX);
1014-
} else if (hasHorizontalIntersection) {
1015-
borderLeftX = extendPointsToTargetY(lto.x, lto.y, lti.x, lti.y, borderTopY);
1016-
borderRightX = extendPointsToTargetY(rto.x, rto.y, rti.x, rti.y, borderTopY);
1017-
}
1018-
1019-
if (topWidth > 0 && borderTopColor?.ios) {
1019+
if (borderWidths.top > 0 && borderTopColor?.ios) {
10201020
const topBorderPath = CGPathCreateMutable();
1021-
const borderTopLeftX: number = extendPointsToTargetY(lto.x, lto.y, lti.x, lti.y, borderTopY);
1022-
const borderTopRightX: number = extendPointsToTargetY(rto.x, rto.y, rti.x, rti.y, borderTopY);
10231021

10241022
CGPathMoveToPoint(topBorderPath, null, lto.x, lto.y);
10251023
CGPathAddLineToPoint(topBorderPath, null, rto.x, rto.y);
1026-
CGPathAddLineToPoint(topBorderPath, null, borderTopRightX, borderTopY);
1027-
if (borderTopRightX !== borderTopLeftX) {
1028-
CGPathAddLineToPoint(topBorderPath, null, borderTopLeftX, borderTopY);
1024+
CGPathAddLineToPoint(topBorderPath, null, rti.x, rti.y);
1025+
if (rti.x !== lti.x) {
1026+
CGPathAddLineToPoint(topBorderPath, null, lti.x, lti.y);
10291027
}
10301028
CGPathAddLineToPoint(topBorderPath, null, lto.x, lto.y);
10311029

10321030
paths[0] = topBorderPath;
10331031
}
1034-
if (rightWidth > 0 && borderRightColor?.ios) {
1032+
if (borderWidths.right > 0 && borderRightColor?.ios) {
10351033
const rightBorderPath = CGPathCreateMutable();
1036-
const borderRightBottomY: number = extendPointsToTargetY(rbo.y, rbo.x, rbi.y, rbi.x, borderRightX);
1037-
const borderRightTopY: number = extendPointsToTargetY(rto.y, rto.x, rti.y, rti.x, borderRightX);
10381034

10391035
CGPathMoveToPoint(rightBorderPath, null, rto.x, rto.y);
10401036
CGPathAddLineToPoint(rightBorderPath, null, rbo.x, rbo.y);
1041-
CGPathAddLineToPoint(rightBorderPath, null, borderRightX, borderRightBottomY);
1042-
if (borderRightBottomY !== borderRightTopY) {
1043-
CGPathAddLineToPoint(rightBorderPath, null, borderRightX, borderRightTopY);
1037+
CGPathAddLineToPoint(rightBorderPath, null, rbi.x, rbi.y);
1038+
if (rbi.y !== rti.y) {
1039+
CGPathAddLineToPoint(rightBorderPath, null, rti.x, rti.y);
10441040
}
10451041
CGPathAddLineToPoint(rightBorderPath, null, rto.x, rto.y);
10461042

10471043
paths[1] = rightBorderPath;
10481044
}
1049-
if (bottomWidth > 0 && borderBottomColor?.ios) {
1045+
if (borderWidths.bottom > 0 && borderBottomColor?.ios) {
10501046
const bottomBorderPath = CGPathCreateMutable();
1051-
const borderBottomLeftX: number = extendPointsToTargetY(lbo.x, lbo.y, lbi.x, lbi.y, borderBottomY);
1052-
const borderBottomRightX: number = extendPointsToTargetY(rbo.x, rbo.y, rbi.x, rbi.y, borderBottomY);
10531047

10541048
CGPathMoveToPoint(bottomBorderPath, null, rbo.x, rbo.y);
10551049
CGPathAddLineToPoint(bottomBorderPath, null, lbo.x, lbo.y);
1056-
CGPathAddLineToPoint(bottomBorderPath, null, borderBottomLeftX, borderBottomY);
1057-
if (borderBottomLeftX !== borderBottomRightX) {
1058-
CGPathAddLineToPoint(bottomBorderPath, null, borderBottomRightX, borderBottomY);
1050+
CGPathAddLineToPoint(bottomBorderPath, null, lbi.x, lbi.y);
1051+
if (lbi.x !== rbi.x) {
1052+
CGPathAddLineToPoint(bottomBorderPath, null, rbi.x, rbi.y);
10591053
}
10601054
CGPathAddLineToPoint(bottomBorderPath, null, rbo.x, rbo.y);
10611055

10621056
paths[2] = bottomBorderPath;
10631057
}
1064-
if (leftWidth > 0 && borderLeftColor?.ios) {
1058+
if (borderWidths.left > 0 && borderLeftColor?.ios) {
10651059
const leftBorderPath = CGPathCreateMutable();
1066-
const borderLeftTopY: number = extendPointsToTargetY(lto.y, lto.x, lti.y, lti.x, borderLeftX);
1067-
const borderLeftBottomY: number = extendPointsToTargetY(lbo.y, lbo.x, lbi.y, lbi.x, borderLeftX);
10681060

10691061
CGPathMoveToPoint(leftBorderPath, null, lbo.x, lbo.y);
10701062
CGPathAddLineToPoint(leftBorderPath, null, lto.x, lto.y);
1071-
CGPathAddLineToPoint(leftBorderPath, null, borderLeftX, borderLeftTopY);
1072-
if (borderLeftTopY !== borderLeftBottomY) {
1073-
CGPathAddLineToPoint(leftBorderPath, null, borderLeftX, borderLeftBottomY);
1063+
CGPathAddLineToPoint(leftBorderPath, null, lti.x, lti.y);
1064+
if (lti.y !== lbi.y) {
1065+
CGPathAddLineToPoint(leftBorderPath, null, lbi.x, lbi.y);
10741066
}
10751067
CGPathAddLineToPoint(leftBorderPath, null, lbo.x, lbo.y);
10761068

packages/core/utils/number-utils.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,3 @@ export const degreesToRadians = (a: number) => a * (Math.PI / 180);
4444
export function valueMap(val: number, in_min: number, in_max: number, out_min: number, out_max: number) {
4545
return ((val - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min;
4646
}
47-
48-
/**
49-
* A method that calculates the target X based on the angle of 2 points and target Y.
50-
*
51-
* @param x1
52-
* @param y1
53-
* @param x2
54-
* @param y2
55-
* @param yTarget
56-
* @returns
57-
*/
58-
export function extendPointsToTargetY(x1: number, y1: number, x2: number, y2: number, yTarget: number) {
59-
const deltaX: number = x2 - x1;
60-
const deltaY: number = y2 - y1;
61-
const angleRadians: number = Math.atan2(deltaY, deltaX);
62-
63-
const targetDeltaY: number = yTarget - y1;
64-
const targetDeltaX: number = targetDeltaY / Math.tan(angleRadians);
65-
66-
return x1 + targetDeltaX;
67-
}

0 commit comments

Comments
 (0)