chore: box shadow updates (#9220)

This commit is contained in:
William Tjondrosuharto
2021-02-20 13:39:06 +07:00
committed by Nathan Walker
parent d2f50e50bb
commit 19ce9fc2bf
9 changed files with 208 additions and 23 deletions

View File

@@ -105,11 +105,6 @@ export class BoxShadowModel extends Observable {
}
this.appliedBoxShadow = this._boxShadow;
this.notifyPropertyChange('appliedBoxShadow', this.appliedBoxShadow);
// TODO: this is a workaround to apply shadow immediately,
// since the box-shadow logic is currently inside background.ts
this.notifyPropertyChange('background', '');
this.notifyPropertyChange('background', this.background);
}
textChange(args): void {

View File

@@ -0,0 +1,145 @@
// This has been disabled/unfinished becase with the current testing setup
// the imports don't work. We need to fully switch to jest & set up ts support
// todo: fix tests.
// import { parseCSSShadow } from "@nativescript/core/ui/styling/css-shadow";
// import { zeroLength } from "@nativescript/core";
// describe('ui', () => {
// describe('styling', () => {
// describe('css-shadow', () => {
//
// it("empty", () => {
// const shadow = parseCSSShadow("")
// expect(shadow.inset).toBe(false)
// expect(shadow.offsetX).toBe(zeroLength)
// expect(shadow.offsetY).toBe(zeroLength)
// expect(shadow.blurRadius).toBe(zeroLength)
// expect(shadow.spreadRadius).toBe(zeroLength)
// expect(shadow.color).toBe('black')
// });
//
// it("1px 1px 2px black", () => {
// const shadow = parseCSSShadow("1px 1px 2px black")
// expect(shadow.inset).toBe(false)
// expect(shadow.offsetX).toBe(1)
// expect(shadow.offsetY).toBe(1)
// expect(shadow.blurRadius).toBe(2)
// expect(shadow.spreadRadius).toBe(zeroLength)
// expect(shadow.color).toBe('black')
// });
//
// it("#fc0 1px 0 10px", () => {
// const shadow = parseCSSShadow("#fc0 1px 0 10px")
// expect(shadow.inset).toBe(false)
// expect(shadow.offsetX).toBe(1)
// expect(shadow.offsetY).toBe(zeroLength)
// expect(shadow.blurRadius).toBe(10)
// expect(shadow.spreadRadius).toBe(zeroLength)
// expect(shadow.color).toBe('#fc0')
// });
//
// it("5px 5px #558abb", () => {
// const shadow = parseCSSShadow("5px 5px #558abb")
// expect(shadow.inset).toBe(false)
// expect(shadow.offsetX).toBe(5)
// expect(shadow.offsetY).toBe(5)
// expect(shadow.blurRadius).toBe(zeroLength)
// expect(shadow.spreadRadius).toBe(zeroLength)
// expect(shadow.color).toBe('#558abb')
// });
//
// it("white 2px 5px", () => {
// const shadow = parseCSSShadow("white 2px 5px")
// expect(shadow.inset).toBe(false)
// expect(shadow.offsetX).toBe(2)
// expect(shadow.offsetY).toBe(5)
// expect(shadow.blurRadius).toBe(zeroLength)
// expect(shadow.spreadRadius).toBe(zeroLength)
// expect(shadow.color).toBe('white')
// });
//
// it("5px 10px", () => {
// const shadow = parseCSSShadow("5px 10px")
// expect(shadow.inset).toBe(false)
// expect(shadow.offsetX).toBe(5)
// expect(shadow.offsetY).toBe(10)
// expect(shadow.blurRadius).toBe(zeroLength)
// expect(shadow.spreadRadius).toBe(zeroLength)
// expect(shadow.color).toBe('black')
// });
//
// // box
// it("60px -16px teal", () => {
// const shadow = parseCSSShadow("60px -16px teal")
// expect(shadow.inset).toBe(false)
// expect(shadow.offsetX).toBe(60)
// expect(shadow.offsetY).toBe(-16)
// expect(shadow.blurRadius).toBe(zeroLength)
// expect(shadow.spreadRadius).toBe(zeroLength)
// expect(shadow.color).toBe('teal')
// });
//
// it("10px 5px 5px black", () => {
// const shadow = parseCSSShadow("10px 5px 5px black")
// expect(shadow.inset).toBe(false)
// expect(shadow.offsetX).toBe(10)
// expect(shadow.offsetY).toBe(5)
// expect(shadow.blurRadius).toBe(zeroLength)
// expect(shadow.spreadRadius).toBe(zeroLength)
// expect(shadow.color).toBe('black')
// });
//
// it("2px 2px 2px 1px rgba(0, 0, 0, 0.2)", () => {
// const shadow = parseCSSShadow("2px 2px 2px 1px rgba(0, 0, 0, 0.2)")
// expect(shadow.inset).toBe(false)
// expect(shadow.offsetX).toBe(2)
// expect(shadow.offsetY).toBe(2)
// expect(shadow.blurRadius).toBe(2)
// expect(shadow.spreadRadius).toBe(1)
// expect(shadow.color).toBe('rgba(0, 0, 0, 0.2)')
// });
//
// it("inset 5em 1em gold", () => {
// const shadow = parseCSSShadow("inset 5em 1em gold")
// expect(shadow.inset).toBe(true)
// expect(shadow.offsetX).toBe(5)
// expect(shadow.offsetY).toBe(1)
// expect(shadow.blurRadius).toBe(zeroLength)
// expect(shadow.spreadRadius).toBe(zeroLength)
// expect(shadow.color).toBe('gold')
// });
//
// it("5 10", () => {
// const shadow = parseCSSShadow("5 10")
// expect(shadow.inset).toBe(false)
// expect(shadow.offsetX).toBe(5)
// expect(shadow.offsetY).toBe(10)
// expect(shadow.blurRadius).toBe(zeroLength)
// expect(shadow.spreadRadius).toBe(zeroLength)
// expect(shadow.color).toBe('black')
// });
//
// it("2 2 2 #333", () => {
// const shadow = parseCSSShadow("2 2 2 #333")
// expect(shadow.inset).toBe(false)
// expect(shadow.offsetX).toBe(2)
// expect(shadow.offsetY).toBe(2)
// expect(shadow.blurRadius).toBe(2)
// expect(shadow.spreadRadius).toBe(zeroLength)
// expect(shadow.color).toBe('#333')
// });
//
// it("-1 -1 1 #333", () => {
// const shadow = parseCSSShadow("-1 -1 1 #333")
// expect(shadow.inset).toBe(false)
// expect(shadow.offsetX).toBe(-1)
// expect(shadow.offsetY).toBe(-1)
// expect(shadow.blurRadius).toBe(1)
// expect(shadow.spreadRadius).toBe(zeroLength)
// expect(shadow.color).toBe('#333')
// });
// });
// });
// });
//

View File

@@ -20,6 +20,7 @@ class AndroidSharedA11YObservable extends SharedA11YObservable {
[accessibilityStateEnabledPropName]: boolean;
[touchExplorationStateEnabledPropName]: boolean;
// @ts-ignore todo: fix
get accessibilityServiceEnabled(): boolean {
return !!this[accessibilityStateEnabledPropName] && !!this[touchExplorationStateEnabledPropName];
}

View File

@@ -4,6 +4,7 @@ import { BackgroundRepeat } from '../styling/style-properties';
import { LinearGradient } from './linear-gradient';
// Types.
import { Color } from '../../color';
import { CSSShadow } from './css-shadow';
export class Background implements BackgroundDefinition {
public static default = new Background();
@@ -26,6 +27,7 @@ export class Background implements BackgroundDefinition {
public borderBottomLeftRadius = 0;
public borderBottomRightRadius = 0;
public clipPath: string;
public boxShadow: CSSShadow;
private clone(): Background {
const clone = new Background();
@@ -48,6 +50,7 @@ export class Background implements BackgroundDefinition {
clone.borderBottomRightRadius = this.borderBottomRightRadius;
clone.borderBottomLeftRadius = this.borderBottomLeftRadius;
clone.clipPath = this.clipPath;
clone.boxShadow = this.boxShadow;
return clone;
}
@@ -178,6 +181,13 @@ export class Background implements BackgroundDefinition {
return clone;
}
public withBoxShadow(value: CSSShadow): Background {
const clone = this.clone();
clone.boxShadow = value;
return clone;
}
public isEmpty(): boolean {
return !this.color && !this.image && !this.hasBorderWidth() && !this.hasBorderRadius() && !this.clipPath;
}
@@ -274,6 +284,14 @@ export class Background implements BackgroundDefinition {
return 0;
}
public hasBoxShadow(): boolean {
return !!this.boxShadow;
}
public getBoxShadow(): CSSShadow {
return this.boxShadow;
}
public toString(): string {
return `isEmpty: ${this.isEmpty()}; color: ${this.color}; image: ${this.image}; repeat: ${this.repeat}; position: ${this.position}; size: ${this.size}; borderTopColor: ${this.borderTopColor}; borderRightColor: ${this.borderRightColor}; borderBottomColor: ${this.borderBottomColor}; borderLeftColor: ${this.borderLeftColor}; borderTopWidth: ${this.borderTopWidth}; borderRightWidth: ${this.borderRightWidth}; borderBottomWidth: ${this.borderBottomWidth}; borderLeftWidth: ${this.borderLeftWidth}; borderTopLeftRadius: ${this.borderTopLeftRadius}; borderTopRightRadius: ${
this.borderTopRightRadius

View File

@@ -6,9 +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';
import { CSSShadow } from './css-shadow';
export * from './background-common';
interface AndroidView {
@@ -93,9 +93,10 @@ export namespace ad {
nativeView.setBackground(defaultDrawable);
}
const boxShadow = view.style.boxShadow;
if (boxShadow) {
drawBoxShadow(nativeView, view, boxShadow);
if (background.hasBoxShadow()) {
drawBoxShadow(nativeView, view, background.getBoxShadow());
} else {
clearBoxShadow(nativeView);
}
// TODO: Can we move BorderWidths as separate native setter?
@@ -226,7 +227,7 @@ function createNativeCSSValueArray(css: string): androidNative.Array<org.natives
return nativeArray;
}
function drawBoxShadow(nativeView: android.view.View, view: View, boxShadow: BoxShadow) {
function drawBoxShadow(nativeView: android.view.View, view: View, boxShadow: CSSShadow) {
const color = boxShadow.color;
const shadowOpacity = color.a;
const shadowColor = new Color(shadowOpacity, color.r, color.g, color.b);
@@ -243,6 +244,10 @@ function drawBoxShadow(nativeView: android.view.View, view: View, boxShadow: Box
org.nativescript.widgets.Utils.drawBoxShadow(nativeView, JSON.stringify(config));
}
function clearBoxShadow(nativeView: android.view.View) {
// org.nativescript.widgets.Utils.clearBoxShadow(nativeView);
}
export enum CacheMode {
none,
memory,

View File

@@ -50,6 +50,7 @@ export declare class Background {
public withBorderBottomRightRadius(value: number): Background;
public withBorderBottomLeftRadius(value: number): Background;
public withClipPath(value: string): Background;
public withBoxShadow(value: BoxShadow): Background;
public isEmpty(): boolean;
@@ -65,6 +66,8 @@ export declare class Background {
public getUniformBorderColor(): Color;
public getUniformBorderWidth(): number;
public getUniformBorderRadius(): number;
public hasBoxShadow(): boolean;
public getBoxShadow(): BoxShadow;
}
export namespace ios {

View File

@@ -8,6 +8,7 @@ import { isDataURI, isFileOrResourcePath, layout } from '../../utils';
import { ImageSource } from '../../image-source';
import { CSSValue, parse as cssParse } from '../../css-value';
import { CSSShadow } from './css-shadow';
import { Length } from './style-properties';
export * from './background-common';
@@ -88,12 +89,11 @@ export namespace ios {
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, view, boxShadow, background);
if (background.hasBoxShadow()) {
drawBoxShadow(nativeView, view, background.getBoxShadow(), background);
} else {
view.setProperty('clipToBounds', true);
clearBoxShadow(nativeView);
}
}
}
@@ -718,23 +718,28 @@ function drawNoRadiusNonUniformBorders(nativeView: NativeView, background: Backg
// TODO: use sublayer if its applied to a layout
function drawBoxShadow(nativeView: NativeView, view: View, boxShadow: CSSShadow, background: BackgroundDefinition, useSubLayer: boolean = false) {
// TODO: fine named 'shadow-layer' first and otherwise need to search through sublayers (need logic used in text-shadow for layer - see getShadowLayer)
const layer: CALayer = nativeView.layer;
layer.masksToBounds = false;
nativeView.clipsToBounds = false;
// this is required (if not, shadow will get cutoff at parent's dimensions)
// nativeView.clipsToBounds doesn't work
view.setProperty('clipToBounds', 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 = layout.toDeviceIndependentPixels(boxShadow.spreadRadius);
layer.shadowOpacity = background.color?.a ? background.color?.a / 255 : 1;
layer.shadowRadius = Length.toDevicePixels(boxShadow.spreadRadius);
layer.shadowColor = boxShadow.color.ios.CGColor;
const adjustedShadowOffset = {
x: layout.toDeviceIndependentPixels(boxShadow.offsetX),
y: layout.toDeviceIndependentPixels(boxShadow.offsetY),
x: Length.toDevicePixels(boxShadow.offsetX),
y: Length.toDevicePixels(boxShadow.offsetY),
};
layer.shadowOffset = CGSizeMake(adjustedShadowOffset.x, adjustedShadowOffset.y);
@@ -745,6 +750,17 @@ function drawBoxShadow(nativeView: NativeView, view: View, boxShadow: CSSShadow,
layer.shadowPath = UIBezierPath.bezierPathWithRoundedRectCornerRadius(nativeView.bounds, cornerRadius).CGPath;
}
function clearBoxShadow(nativeView: NativeView) {
nativeView.clipsToBounds = true;
const layer: CALayer = nativeView.layer;
layer.masksToBounds = true;
layer.shadowOffset = CGSizeMake(0, 0);
layer.shadowColor = UIColor.clearColor.CGColor;
layer.cornerRadius = 0.0;
layer.shadowRadius = 0.0;
layer.shadowOpacity = 0.0;
}
function drawGradient(nativeView: NativeView, gradient: LinearGradient) {
const gradientLayer = CAGradientLayer.layer();
gradientLayer.frame = nativeView.bounds;

View File

@@ -1280,7 +1280,8 @@ const boxShadowProperty = new CssProperty<Style, CSSShadow>({
name: 'boxShadow',
cssName: 'box-shadow',
valueChanged: (target, oldValue, newValue) => {
target.boxShadow = newValue;
const background = target.backgroundInternal.withBoxShadow(newValue);
target.backgroundInternal = background;
},
valueConverter: (value) => {
return parseCSSShadow(value);

View File

@@ -1,5 +1,6 @@
// Types
import { TextDecoration, TextAlignment, TextTransform, TextShadow, WhiteSpace, getClosestPropertyValue } from './text-base-common';
import { TextDecoration, TextAlignment, TextTransform, WhiteSpace, getClosestPropertyValue } from './text-base-common';
import { CSSShadow } from '../styling/css-shadow';
// Requires
import { Font } from '../styling/font';
@@ -387,7 +388,7 @@ export class TextBase extends TextBaseCommon {
};
}
[textShadowProperty.setNative](value: TextShadow) {
[textShadowProperty.setNative](value: CSSShadow) {
this.nativeViewProtected.setShadowLayer(Length.toDevicePixels(value.blurRadius, 0), Length.toDevicePixels(value.offsetX, 0), Length.toDevicePixels(value.offsetY, 0), value.color.android);
}