mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-16 11:42:04 +08:00
feat(core): box-shadow support (#9161)
This commit is contained in:
6
packages/core/ui/core/view/index.d.ts
vendored
6
packages/core/ui/core/view/index.d.ts
vendored
@ -6,6 +6,7 @@ import { Animation, AnimationDefinition, AnimationPromise } from '../../animatio
|
||||
import { HorizontalAlignment, VerticalAlignment, Visibility, Length, PercentLength } from '../../styling/style-properties';
|
||||
import { GestureTypes, GestureEventData, GesturesObserver } from '../../gestures';
|
||||
import { LinearGradient } from '../../styling/gradient';
|
||||
import { BoxShadow } from '../../styling/box-shadow';
|
||||
|
||||
// helpers (these are okay re-exported here)
|
||||
export * from './view-helper';
|
||||
@ -250,6 +251,11 @@ export abstract class View extends ViewBase {
|
||||
*/
|
||||
backgroundImage: string | LinearGradient;
|
||||
|
||||
/**
|
||||
* Gets or sets the box shadow of the view.
|
||||
*/
|
||||
boxShadow: string | BoxShadow;
|
||||
|
||||
/**
|
||||
* Gets or sets the minimum width the view may grow to.
|
||||
*/
|
||||
|
@ -22,6 +22,7 @@ import { LinearGradient } from '../../styling/linear-gradient';
|
||||
import { TextTransform } from '../../text-base';
|
||||
|
||||
import * as am from '../../animation';
|
||||
import { BoxShadow } from '../../styling/box-shadow';
|
||||
|
||||
// helpers (these are okay re-exported here)
|
||||
export * from './view-helper';
|
||||
@ -581,6 +582,13 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
this.style.backgroundRepeat = value;
|
||||
}
|
||||
|
||||
get boxShadow(): BoxShadow {
|
||||
return this.style.boxShadow;
|
||||
}
|
||||
set boxShadow(value: BoxShadow) {
|
||||
this.style.boxShadow = value;
|
||||
}
|
||||
|
||||
get minWidth(): Length {
|
||||
return this.style.minWidth;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ export class LayoutBase extends LayoutBaseCommon {
|
||||
|
||||
_setNativeClipToBounds() {
|
||||
if (this.clipToBounds) {
|
||||
// TODO: temporarily setting this to false as it crops the shadow
|
||||
this.nativeViewProtected.clipsToBounds = true;
|
||||
} else {
|
||||
super._setNativeClipToBounds();
|
||||
|
@ -6,6 +6,9 @@ import { parse } from '../../css-value';
|
||||
import { path, knownFolders } from '../../file-system';
|
||||
import * as application from '../../application';
|
||||
import { profile } from '../../profiling';
|
||||
import { BoxShadow } from './box-shadow';
|
||||
import { Color } from '../../color';
|
||||
import { Screen } from '../../platform';
|
||||
export * from './background-common';
|
||||
|
||||
interface AndroidView {
|
||||
@ -90,6 +93,11 @@ export namespace ad {
|
||||
nativeView.setBackground(defaultDrawable);
|
||||
}
|
||||
|
||||
const boxShadow = view.style.boxShadow;
|
||||
if (boxShadow) {
|
||||
drawBoxShadow(nativeView, boxShadow);
|
||||
}
|
||||
|
||||
// TODO: Can we move BorderWidths as separate native setter?
|
||||
// This way we could skip setPadding if borderWidth is not changed.
|
||||
const leftPadding = Math.ceil(view.effectiveBorderLeftWidth + view.effectivePaddingLeft);
|
||||
@ -218,6 +226,24 @@ function createNativeCSSValueArray(css: string): native.Array<org.nativescript.w
|
||||
return nativeArray;
|
||||
}
|
||||
|
||||
function drawBoxShadow(nativeView: android.view.View, boxShadow: BoxShadow) {
|
||||
const color = boxShadow.color;
|
||||
const shadowOpacity = color.a;
|
||||
const shadowColor = new Color(shadowOpacity, color.r, color.g, color.b);
|
||||
// TODO: corner radius here should reflect the view's corner radius
|
||||
const cornerRadius = 0; // this should be applied to the main view as well (try 20 with a transparent background on the xml to see the effect)
|
||||
const config = {
|
||||
shadowColor: shadowColor.android,
|
||||
cornerRadius,
|
||||
spreadRadius: boxShadow.spreadRadius,
|
||||
blurRadius: boxShadow.blurRadius,
|
||||
offsetX: boxShadow.offsetX,
|
||||
offsetY: boxShadow.offsetY,
|
||||
scale: Screen.mainScreen.scale,
|
||||
};
|
||||
org.nativescript.widgets.Utils.drawBoxShadow(nativeView, JSON.stringify(config));
|
||||
}
|
||||
|
||||
export enum CacheMode {
|
||||
none,
|
||||
memory,
|
||||
|
2
packages/core/ui/styling/background.d.ts
vendored
2
packages/core/ui/styling/background.d.ts
vendored
@ -2,6 +2,7 @@ import { Color } from '../../color';
|
||||
import { View } from '../core/view';
|
||||
import { BackgroundRepeat } from '../../css/parser';
|
||||
import { LinearGradient } from '../styling/linear-gradient';
|
||||
import { BoxShadow } from './box-shadow';
|
||||
|
||||
export enum CacheMode {
|
||||
none,
|
||||
@ -29,6 +30,7 @@ export declare class Background {
|
||||
public borderBottomRightRadius: number;
|
||||
public borderBottomLeftRadius: number;
|
||||
public clipPath: string;
|
||||
public boxShadow: string | BoxShadow;
|
||||
|
||||
public withColor(value: Color): Background;
|
||||
public withImage(value: string | LinearGradient): Background;
|
||||
|
@ -7,6 +7,9 @@ import { Color } from '../../color';
|
||||
import { isDataURI, isFileOrResourcePath, layout } from '../../utils';
|
||||
import { ImageSource } from '../../image-source';
|
||||
import { CSSValue, parse as cssParse } from '../../css-value';
|
||||
import { BoxShadow } from './box-shadow';
|
||||
import { Screen } from '../../platform';
|
||||
import { StackLayout } from '../layouts/stack-layout';
|
||||
|
||||
export * from './background-common';
|
||||
|
||||
@ -24,6 +27,7 @@ interface NativeView extends UIView {
|
||||
leftBorderLayer: CALayer;
|
||||
|
||||
gradientLayer: CAGradientLayer;
|
||||
boxShadowLayer: CALayer;
|
||||
}
|
||||
|
||||
interface Rect {
|
||||
@ -85,6 +89,15 @@ export namespace ios {
|
||||
} else {
|
||||
setUIColorFromImage(view, nativeView, callback, flip);
|
||||
}
|
||||
|
||||
const boxShadow = view.style.boxShadow;
|
||||
if (boxShadow) {
|
||||
|
||||
// this is required (if not, shadow will get cutoff at parent's dimensions)
|
||||
// nativeView.clipsToBounds doesn't work
|
||||
view.setProperty('clipToBounds', false);
|
||||
drawBoxShadow(nativeView, boxShadow, background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -706,6 +719,46 @@ function drawNoRadiusNonUniformBorders(nativeView: NativeView, background: Backg
|
||||
nativeView.hasNonUniformBorder = hasNonUniformBorder;
|
||||
}
|
||||
|
||||
// TODO: use sublayer if its applied to a layout
|
||||
function drawBoxShadow(nativeView: NativeView, boxShadow: BoxShadow, background: BackgroundDefinition, useSubLayer: boolean = false) {
|
||||
const layer: CALayer = nativeView.layer;
|
||||
|
||||
layer.masksToBounds = false;
|
||||
nativeView.clipsToBounds = false;
|
||||
|
||||
if (!background.color.a) {
|
||||
// add white background if view has a transparent background
|
||||
layer.backgroundColor = UIColor.whiteColor.CGColor;
|
||||
}
|
||||
// shadow opacity is handled on the shadow's color instance
|
||||
layer.shadowOpacity = 1;
|
||||
layer.shadowRadius = boxShadow.spreadRadius;
|
||||
layer.shadowColor = boxShadow.color.ios.CGColor;
|
||||
|
||||
// / 2 here since ios's shadow offset is bigger than android
|
||||
// TODO: this is just for experimenting with the amount of offset,
|
||||
// need to use some real calculation here to gain parity with android's
|
||||
// implementation
|
||||
const adjustedShadowOffset = {
|
||||
x: boxShadow.offsetX / 2,
|
||||
y: boxShadow.offsetY / 2,
|
||||
};
|
||||
layer.shadowOffset = CGSizeMake(adjustedShadowOffset.x, adjustedShadowOffset.y);
|
||||
|
||||
// this should match the view's border radius
|
||||
const cornerRadius = 0;
|
||||
// This doesn't handle the offsets properly
|
||||
// factor in shadowRadius and the offsets so shadow don't spread too far
|
||||
// layer.shadowPath = UIBezierPath.bezierPathWithRoundedRectCornerRadius(CGRectMake(
|
||||
// nativeView.bounds.origin.x + boxShadow.spreadRadius + adjustedShadowOffset.x,
|
||||
// nativeView.bounds.origin.y + boxShadow.spreadRadius + adjustedShadowOffset.y,
|
||||
// nativeView.bounds.size.width - boxShadow.spreadRadius - adjustedShadowOffset.x,
|
||||
// nativeView.bounds.size.height - boxShadow.spreadRadius - adjustedShadowOffset.y), cornerRadius).CGPath;
|
||||
|
||||
// This has the nice glow with box shadow of 0,0
|
||||
layer.shadowPath = UIBezierPath.bezierPathWithRoundedRectCornerRadius(nativeView.bounds, cornerRadius).CGPath;
|
||||
}
|
||||
|
||||
function drawGradient(nativeView: NativeView, gradient: LinearGradient) {
|
||||
const gradientLayer = CAGradientLayer.layer();
|
||||
gradientLayer.frame = nativeView.bounds;
|
||||
|
9
packages/core/ui/styling/box-shadow.ts
Normal file
9
packages/core/ui/styling/box-shadow.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Color } from '../../color';
|
||||
|
||||
export class BoxShadow {
|
||||
public offsetX: number;
|
||||
public offsetY: number;
|
||||
public blurRadius: number;
|
||||
public spreadRadius: number;
|
||||
public color: Color;
|
||||
}
|
@ -17,6 +17,7 @@ import { Trace } from '../../trace';
|
||||
|
||||
import * as parser from '../../css/parser';
|
||||
import { LinearGradient } from './linear-gradient';
|
||||
import { BoxShadow } from './box-shadow';
|
||||
|
||||
export type LengthDipUnit = { readonly unit: 'dip'; readonly value: dip };
|
||||
export type LengthPxUnit = { readonly unit: 'px'; readonly value: px };
|
||||
@ -451,6 +452,51 @@ export const verticalAlignmentProperty = new CssProperty<Style, VerticalAlignmen
|
||||
});
|
||||
verticalAlignmentProperty.register(Style);
|
||||
|
||||
function parseBoxShadowProperites(value: string): BoxShadow {
|
||||
if (typeof value === 'string') {
|
||||
let arr;
|
||||
let colorRaw;
|
||||
if (value.indexOf('rgb') > -1) {
|
||||
arr = value.split(' ');
|
||||
colorRaw = arr.pop();
|
||||
} else {
|
||||
arr = value.split(/[ ,]+/);
|
||||
colorRaw = arr.pop();
|
||||
}
|
||||
|
||||
let offsetX: number;
|
||||
let offsetY: number;
|
||||
let blurRadius: number; // not currently in use
|
||||
let spreadRadius: number; // maybe rename this to just radius
|
||||
let color: Color = new Color(colorRaw);
|
||||
|
||||
if (arr.length === 2) {
|
||||
offsetX = parseFloat(arr[0]);
|
||||
offsetY = parseFloat(arr[1]);
|
||||
} else if (arr.length === 3) {
|
||||
offsetX = parseFloat(arr[0]);
|
||||
offsetY = parseFloat(arr[1]);
|
||||
blurRadius = parseFloat(arr[2]);
|
||||
} else if (arr.length === 4) {
|
||||
offsetX = parseFloat(arr[0]);
|
||||
offsetY = parseFloat(arr[1]);
|
||||
blurRadius = parseFloat(arr[2]);
|
||||
spreadRadius = parseFloat(arr[3]);
|
||||
} else {
|
||||
throw new Error('Expected 3, 4 or 5 parameters. Actual: ' + value);
|
||||
}
|
||||
return {
|
||||
offsetX: offsetX,
|
||||
offsetY: offsetY,
|
||||
blurRadius: blurRadius,
|
||||
spreadRadius: spreadRadius,
|
||||
color: color,
|
||||
};
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
interface Thickness {
|
||||
top: string;
|
||||
right: string;
|
||||
@ -1275,6 +1321,18 @@ export const borderBottomLeftRadiusProperty = new CssProperty<Style, Length>({
|
||||
});
|
||||
borderBottomLeftRadiusProperty.register(Style);
|
||||
|
||||
const boxShadowProperty = new CssProperty<Style, BoxShadow>({
|
||||
name: 'boxShadow',
|
||||
cssName: 'box-shadow',
|
||||
valueChanged: (target, oldValue, newValue) => {
|
||||
target.boxShadow = newValue;
|
||||
},
|
||||
valueConverter: (value) => {
|
||||
return parseBoxShadowProperites(value);
|
||||
},
|
||||
});
|
||||
boxShadowProperty.register(Style);
|
||||
|
||||
function isNonNegativeFiniteNumber(value: number): boolean {
|
||||
return isFinite(value) && !isNaN(value) && value >= 0;
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { Observable } from '../../../data/observable';
|
||||
import { FlexDirection, FlexWrap, JustifyContent, AlignItems, AlignContent, Order, FlexGrow, FlexShrink, FlexWrapBefore, AlignSelf } from '../../layouts/flexbox-layout';
|
||||
import { Trace } from '../../../trace';
|
||||
import { TextAlignment, TextDecoration, TextTransform, WhiteSpace } from '../../text-base';
|
||||
import { BoxShadow } from '../box-shadow';
|
||||
|
||||
export interface CommonLayoutParams {
|
||||
width: number;
|
||||
@ -137,6 +138,8 @@ export class Style extends Observable implements StyleDefinition {
|
||||
public borderBottomRightRadius: Length;
|
||||
public borderBottomLeftRadius: Length;
|
||||
|
||||
public boxShadow: BoxShadow;
|
||||
|
||||
public fontSize: number;
|
||||
public fontFamily: string;
|
||||
public fontStyle: FontStyle;
|
||||
|
Reference in New Issue
Block a user