Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions apps/toolbox/src/pages/box-shadow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ export class BoxShadowModel extends Observable {
private _selectedBackgroundType: string;
private _selectedBorderType: string;
private _selectedAnimation: string;
private _boxShadow: string = '0 0 2 2 rgba(200, 0, 0, 0.4)';
// private _boxShadow: string = '5 5 1 1 rgba(255, 0, 0, .9)';
// private _boxShadow: string = '5 5 5 10 rgba(255, 0, 0, .9)';
private _boxShadow: string = '-5 -5 5 5 rgba(200, 0, 0, 0.5), 5 5 5 5 rgba(0, 99, 0, 0.5)';

background: string;
borderColor: string;
Expand Down Expand Up @@ -136,7 +134,7 @@ export class BoxShadowModel extends Observable {
view.animate({
width: originalWidth,
duration: animationDuration,
})
}),
)
.catch((err) => {
console.error('animation error', err);
Expand All @@ -152,7 +150,7 @@ export class BoxShadowModel extends Observable {
view.animate({
height: originalHeight,
duration: animationDuration,
})
}),
)
.catch((err) => {
console.error('animation error', err);
Expand All @@ -173,7 +171,7 @@ export class BoxShadowModel extends Observable {
rotate: 0,
translate: { x: 0, y: 0 },
duration: 500,
})
}),
)
.catch((err) => {
console.error('animation error', err);
Expand Down
32 changes: 19 additions & 13 deletions packages/core/ui/core/view/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1507,17 +1507,23 @@ export class View extends ViewCommon {
}
}

protected _drawBoxShadow(boxShadow: BoxShadow) {
protected _drawBoxShadow(boxShadows: BoxShadow[]) {
const nativeView = this.nativeViewProtected;
const config = {
shadowColor: boxShadow.color.android,
cornerRadius: Length.toDevicePixels(this.borderRadius as CoreTypes.LengthType, 0.0),
spreadRadius: boxShadow.spreadRadius,
blurRadius: boxShadow.blurRadius,
offsetX: boxShadow.offsetX,
offsetY: boxShadow.offsetY,
};
org.nativescript.widgets.Utils.drawBoxShadow(nativeView, JSON.stringify(config));
const valueCount = 6;
const nativeArray: number[] = Array.create('int', boxShadows.length * valueCount);

for (let i = 0, length = boxShadows.length; i < length; i++) {
const boxShadow = boxShadows[i];
const nativeIndex = i * valueCount;

nativeArray[nativeIndex + 0] = boxShadow.color.android;
nativeArray[nativeIndex + 1] = boxShadow.spreadRadius;
nativeArray[nativeIndex + 2] = boxShadow.blurRadius;
nativeArray[nativeIndex + 3] = boxShadow.offsetX;
nativeArray[nativeIndex + 4] = boxShadow.offsetY;
nativeArray[nativeIndex + 5] = boxShadow.inset ? 1 : 0;
}
org.nativescript.widgets.Utils.drawBoxShadow(nativeView, nativeArray);
}

_redrawNativeBackground(value: android.graphics.drawable.Drawable | Background): void {
Expand Down Expand Up @@ -1566,15 +1572,15 @@ export class View extends ViewCommon {
// prettier-ignore
const onlyColor = !background.hasBorderWidth()
&& !background.hasBorderRadius()
&& !background.hasBoxShadow()
&& !background.hasBoxShadows()
&& !background.clipPath
&& !background.image
&& !!background.color;

this._applyBackground(background, isBorderDrawable, onlyColor, drawable);

if (background.hasBoxShadow()) {
this._drawBoxShadow(background.getBoxShadow());
if (background.hasBoxShadows()) {
this._drawBoxShadow(background.getBoxShadows());
}

// TODO: Can we move BorderWidths as separate native setter?
Expand Down
2 changes: 1 addition & 1 deletion packages/core/ui/core/view/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ export abstract class View extends ViewCommon {
*
* @nsProperty
*/
boxShadow: string | ShadowCSSValues;
boxShadow: string | ShadowCSSValues[];

/**
* Gets or sets the minimum width the view may grow to.
Expand Down
2 changes: 1 addition & 1 deletion packages/core/ui/core/view/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ export class View extends ViewCommon {
}

const background = this.style.backgroundInternal;
const backgroundDependsOnSize = (background.image && background.image !== 'none') || background.clipPath || !background.hasUniformBorder() || background.hasBorderRadius() || background.hasBoxShadow();
const backgroundDependsOnSize = (background.image && background.image !== 'none') || background.clipPath || !background.hasUniformBorder() || background.hasBorderRadius() || background.hasBoxShadows();

if (this._nativeBackgroundState === 'invalid' || (this._nativeBackgroundState === 'drawn' && backgroundDependsOnSize)) {
this._redrawNativeBackground(background);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/ui/core/view/view-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -724,10 +724,10 @@ export abstract class ViewCommon extends ViewBase {
this.style.backgroundRepeat = value;
}

get boxShadow(): ShadowCSSValues {
get boxShadow(): string | ShadowCSSValues[] {
return this.style.boxShadow;
}
set boxShadow(value: ShadowCSSValues) {
set boxShadow(value: string | ShadowCSSValues[]) {
this.style.boxShadow = value;
}

Expand Down
18 changes: 9 additions & 9 deletions packages/core/ui/styling/background-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class Background {
public borderBottomLeftRadius = 0;
public borderBottomRightRadius = 0;
public clipPath: string | ClipPathFunction;
public boxShadow: BoxShadow;
public boxShadows: BoxShadow[];
public clearFlags: number = BackgroundClearFlags.NONE;

private clone(): Background {
Expand All @@ -64,7 +64,7 @@ export class Background {
clone.borderBottomRightRadius = this.borderBottomRightRadius;
clone.borderBottomLeftRadius = this.borderBottomLeftRadius;
clone.clipPath = this.clipPath;
clone.boxShadow = this.boxShadow;
clone.boxShadows = this.boxShadows;
clone.clearFlags = this.clearFlags;

return clone;
Expand Down Expand Up @@ -199,10 +199,10 @@ export class Background {
return clone;
}

public withBoxShadow(value: BoxShadow): Background {
public withBoxShadows(value: BoxShadow[]): Background {
const clone = this.clone();
clone.boxShadow = value;
if (!value) {
clone.boxShadows = value;
if (!value?.length) {
clone.clearFlags |= BackgroundClearFlags.CLEAR_BOX_SHADOW;
}

Expand Down Expand Up @@ -317,12 +317,12 @@ export class Background {
return 0;
}

public hasBoxShadow(): boolean {
return !!this.boxShadow;
public hasBoxShadows(): boolean {
return this.boxShadows?.length > 0;
}

public getBoxShadow(): BoxShadow {
return this.boxShadow;
public getBoxShadows(): BoxShadow[] {
return this.boxShadows;
}

public toString(): string {
Expand Down
3 changes: 0 additions & 3 deletions packages/core/ui/styling/background.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export enum CacheMode {
// public borderBottomRightRadius: number;
// public borderBottomLeftRadius: number;
// public clipPath: string | ClipPathFunction;
// public boxShadow: string | BoxShadow;
// public clearFlags: number;

// public withColor(value: Color): Background;
Expand Down Expand Up @@ -72,8 +71,6 @@ export enum CacheMode {
// public getUniformBorderColor(): Color;
// public getUniformBorderWidth(): number;
// public getUniformBorderRadius(): number;
// public hasBoxShadow(): boolean;
// public getBoxShadow(): BoxShadow;
// }

export namespace ios {
Expand Down
93 changes: 51 additions & 42 deletions packages/core/ui/styling/background.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export namespace ios {
layer.mask.path = generateClipPath(view, layer.bounds);
}

if (background.hasBoxShadow()) {
if (background.hasBoxShadows()) {
drawBoxShadow(view);
needsLayerAdjustmentOnScroll = true;
}
Expand Down Expand Up @@ -214,12 +214,10 @@ export namespace ios {
callback(generatePatternImage(bitmap, view, flip));
}

export function generateShadowLayerPaths(view: View, bounds: CGRect): { maskPath: any; shadowPath: any } {
export function generateShadowLayerPaths(view: View, boxShadow: BoxShadow, bounds: CGRect): { maskPath: any; shadowPath: any } {
const background = view.style.backgroundInternal;
const nativeView = <NativeScriptUIView>view.nativeViewProtected;
const layer = nativeView.layer;

const boxShadow: BoxShadow = background.getBoxShadow();
const spreadRadius = layout.toDeviceIndependentPixels(boxShadow.spreadRadius);

const { width, height } = bounds.size;
Expand Down Expand Up @@ -1080,65 +1078,56 @@ function drawBoxShadow(view: View): void {
}

const bounds = nativeView.bounds;
const boxShadow: BoxShadow = background.getBoxShadow();
const boxShadows: BoxShadow[] = background.getBoxShadows();

// Initialize outer shadows
let outerShadowContainerLayer: CALayer;
let shadowGroupLayer: CALayer;

if (nativeView.outerShadowContainerLayer) {
outerShadowContainerLayer = nativeView.outerShadowContainerLayer;
shadowGroupLayer = nativeView.outerShadowContainerLayer;
} else {
outerShadowContainerLayer = CALayer.new();

// TODO: Make this dynamic when views get support for multiple shadows
const shadowLayer = CALayer.new();
// This mask is necessary to maintain transparent background
const maskLayer = CAShapeLayer.new();
maskLayer.fillRule = kCAFillRuleEvenOdd;

shadowLayer.mask = maskLayer;
outerShadowContainerLayer.addSublayer(shadowLayer);
shadowGroupLayer = CALayer.new();

// Instead of nesting it, add shadow container layer underneath view so that it's not affected by border masking
layer.superlayer.insertSublayerBelow(outerShadowContainerLayer, layer);
nativeView.outerShadowContainerLayer = outerShadowContainerLayer;
layer.superlayer.insertSublayerBelow(shadowGroupLayer, layer);
nativeView.outerShadowContainerLayer = shadowGroupLayer;
}

// Apply clip path to shadow
if (nativeView.maskType === iosViewUtils.LayerMask.CLIP_PATH && layer.mask instanceof CAShapeLayer) {
if (!outerShadowContainerLayer.mask) {
outerShadowContainerLayer.mask = CAShapeLayer.new();
// Check if the number of shadow layers is correct
if (!shadowGroupLayer.sublayers || boxShadows.length > shadowGroupLayer.sublayers.count) {
const length = shadowGroupLayer.sublayers ? boxShadows.length - shadowGroupLayer.sublayers.count : boxShadows.length;

for (let i = 0; i < length; i++) {
const shadowLayer = CALayer.new();
// This mask is necessary to maintain transparent background
const maskLayer = CAShapeLayer.new();

maskLayer.fillRule = kCAFillRuleEvenOdd;
shadowLayer.mask = maskLayer;
shadowGroupLayer.addSublayer(shadowLayer);
}
if (outerShadowContainerLayer.mask instanceof CAShapeLayer) {
outerShadowContainerLayer.mask.path = layer.mask.path;
} else if (shadowGroupLayer.sublayers && boxShadows.length < shadowGroupLayer.sublayers.count) {
for (let i = 0, length = shadowGroupLayer.sublayers.count - boxShadows.length; i < length; i++) {
shadowGroupLayer.sublayers[i].removeFromSuperlayer();
}
}

outerShadowContainerLayer.bounds = bounds;
outerShadowContainerLayer.transform = layer.transform;
outerShadowContainerLayer.anchorPoint = layer.anchorPoint;
outerShadowContainerLayer.position = nativeView.center;
outerShadowContainerLayer.zPosition = layer.zPosition;

// Inherit view visibility values
outerShadowContainerLayer.opacity = layer.opacity;
outerShadowContainerLayer.hidden = layer.hidden;

const outerShadowLayers = outerShadowContainerLayer.sublayers;
if (outerShadowLayers?.count) {
for (let i = 0, count = outerShadowLayers.count; i < count; i++) {
const shadowLayer = outerShadowLayers[i];
if (shadowGroupLayer.sublayers?.count) {
for (let i = 0, count = shadowGroupLayer.sublayers.count; i < count; i++) {
const shadowLayer = shadowGroupLayer.sublayers[i];
const boxShadow = boxShadows[i];
const shadowRadius = layout.toDeviceIndependentPixels(boxShadow.blurRadius);
const spreadRadius = layout.toDeviceIndependentPixels(boxShadow.spreadRadius);
const offsetX = layout.toDeviceIndependentPixels(boxShadow.offsetX);
const offsetY = layout.toDeviceIndependentPixels(boxShadow.offsetY);
const { maskPath, shadowPath } = ios.generateShadowLayerPaths(view, bounds);
const { maskPath, shadowPath } = ios.generateShadowLayerPaths(view, boxShadow, bounds);

shadowLayer.allowsEdgeAntialiasing = true;
shadowLayer.contentsScale = Screen.mainScreen.scale;

// Shadow opacity is handled on the shadow's color instance
shadowLayer.shadowOpacity = boxShadow.color?.a ? boxShadow.color.a / 255 : 1;
shadowLayer.shadowRadius = shadowRadius;
// Use this multiplier to imitate CSS shadow blur
shadowLayer.shadowRadius = shadowRadius * 0.5;
shadowLayer.shadowColor = boxShadow.color?.ios?.CGColor;
shadowLayer.shadowOffset = CGSizeMake(offsetX, offsetY);

Expand All @@ -1151,6 +1140,26 @@ function drawBoxShadow(view: View): void {
}
}
}

// Apply clip path to shadow
if (nativeView.maskType === iosViewUtils.LayerMask.CLIP_PATH && layer.mask instanceof CAShapeLayer) {
if (!shadowGroupLayer.mask) {
shadowGroupLayer.mask = CAShapeLayer.new();
}
if (shadowGroupLayer.mask instanceof CAShapeLayer) {
shadowGroupLayer.mask.path = layer.mask.path;
}
}

shadowGroupLayer.bounds = bounds;
shadowGroupLayer.transform = layer.transform;
shadowGroupLayer.anchorPoint = layer.anchorPoint;
shadowGroupLayer.position = nativeView.center;
shadowGroupLayer.zPosition = layer.zPosition;

// Inherit view visibility values
shadowGroupLayer.opacity = layer.opacity;
shadowGroupLayer.hidden = layer.hidden;
}

function clearBoxShadow(nativeView: NativeScriptUIView) {
Expand Down
23 changes: 19 additions & 4 deletions packages/core/ui/styling/css-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Trace } from '../../trace';
import { CoreTypes } from '../../core-types';
import { Trace } from '../../trace';
import { Length } from './length-shared';

export function cleanupImportantFlags(value: unknown, propertyName: string) {
Expand All @@ -18,9 +18,14 @@ export function cleanupImportantFlags(value: unknown, propertyName: string) {
}

/**
* Matches whitespace except if the whitespace is contained in parenthesis - ex. rgb(a), hsl color.
* Matches whitespace except if the whitespace is contained in parenthesis - e.g. rgb(a), hsl color.
*/
const WHITE_SPACE_RE = /\s(?![^(]*\))/;

/**
* Matches comma except if the comma is contained in parenthesis - e.g. rgb(a, b, c).
*/
const PARTS_RE = /\s(?![^(]*\))/;
const COMMA_RE = /,(?![^(]*\))/;

/**
* Matches a Length value with or without a unit
Expand All @@ -32,12 +37,22 @@ const LENGTH_RE = /^-?[0-9]+[a-zA-Z%]*?$/;
*/
const isLength = (v) => v === '0' || LENGTH_RE.test(v);

export function parseCSSCommaSeparatedListOfValues(value: string): string[] {
const values: string[] = [];

if (!value) {
return [];
}

return value.split(COMMA_RE);
}

export function parseCSSShorthand(value: string): {
values: Array<CoreTypes.LengthType>;
color: string;
inset: boolean;
} {
const parts = value.trim().split(PARTS_RE);
const parts = value.trim().split(WHITE_SPACE_RE);
const first = parts[0];

if (['', 'none', 'unset'].includes(first)) {
Expand Down
Loading