mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-17 04:41:36 +08:00
feat(root-layout): support gradient colors on shade cover (#9626)
Co-authored-by: William Juan <williamjuan027@gmail.com>
This commit is contained in:
@ -11,7 +11,7 @@ export class RootLayoutModel extends Observable {
|
||||
view: this.getPopup('#EA5936', 110, -30),
|
||||
options: {
|
||||
shadeCover: {
|
||||
color: '#FFF',
|
||||
color: 'linear-gradient(to bottom, red, blue)',
|
||||
opacity: 0.7,
|
||||
tapToClose: true,
|
||||
},
|
||||
|
2
packages/core/ui/core/view/index.d.ts
vendored
2
packages/core/ui/core/view/index.d.ts
vendored
@ -4,7 +4,7 @@ import { EventData } from '../../../data/observable';
|
||||
import { Color } from '../../../color';
|
||||
import { Animation, AnimationDefinition, AnimationPromise } from '../../animation';
|
||||
import { GestureTypes, GesturesObserver } from '../../gestures';
|
||||
import { LinearGradient } from '../../styling/gradient';
|
||||
import { LinearGradient } from '../../styling/linear-gradient';
|
||||
import { AccessibilityLiveRegion, AccessibilityRole, AccessibilityState, AccessibilityTrait, AccessibilityEventOptions } from '../../../accessibility/accessibility-types';
|
||||
import { CoreTypes } from '../../../core-types';
|
||||
import { CSSShadow } from '../../styling/css-shadow';
|
||||
|
@ -2,6 +2,8 @@ import { Color } from '../../../color';
|
||||
import { View } from '../../core/view';
|
||||
import { RootLayoutBase, defaultShadeCoverOptions } from './root-layout-common';
|
||||
import { TransitionAnimation, ShadeCoverOptions } from '.';
|
||||
import { parseLinearGradient } from '../../../css/parser';
|
||||
import { LinearGradient } from '../../styling/linear-gradient';
|
||||
|
||||
export * from './root-layout-common';
|
||||
|
||||
@ -74,14 +76,28 @@ export class RootLayout extends RootLayoutBase {
|
||||
}
|
||||
|
||||
private _getAnimationSet(view: View, shadeCoverAnimation: TransitionAnimation, backgroundColor: string = defaultShadeCoverOptions.color): Array<android.animation.Animator> {
|
||||
const animationSet = Array.create(android.animation.Animator, 7);
|
||||
const backgroundIsGradient = backgroundColor.startsWith('linear-gradient');
|
||||
|
||||
const animationSet = Array.create(android.animation.Animator, backgroundIsGradient ? 6 : 7);
|
||||
animationSet[0] = android.animation.ObjectAnimator.ofFloat(view.nativeViewProtected, 'translationX', [shadeCoverAnimation.translateX]);
|
||||
animationSet[1] = android.animation.ObjectAnimator.ofFloat(view.nativeViewProtected, 'translationY', [shadeCoverAnimation.translateY]);
|
||||
animationSet[2] = android.animation.ObjectAnimator.ofFloat(view.nativeViewProtected, 'scaleX', [shadeCoverAnimation.scaleX]);
|
||||
animationSet[3] = android.animation.ObjectAnimator.ofFloat(view.nativeViewProtected, 'scaleY', [shadeCoverAnimation.scaleY]);
|
||||
animationSet[4] = android.animation.ObjectAnimator.ofFloat(view.nativeViewProtected, 'rotation', [shadeCoverAnimation.rotate]);
|
||||
animationSet[5] = android.animation.ObjectAnimator.ofFloat(view.nativeViewProtected, 'alpha', [shadeCoverAnimation.opacity]);
|
||||
animationSet[6] = this._getBackgroundColorAnimator(view, backgroundColor);
|
||||
|
||||
if (backgroundIsGradient) {
|
||||
if (view.backgroundColor) {
|
||||
view.backgroundColor = undefined;
|
||||
}
|
||||
const parsedGradient = parseLinearGradient(backgroundColor);
|
||||
view.backgroundImage = LinearGradient.parse(parsedGradient.value);
|
||||
} else {
|
||||
if (view.backgroundImage) {
|
||||
view.backgroundImage = undefined;
|
||||
}
|
||||
animationSet[6] = this._getBackgroundColorAnimator(view, backgroundColor);
|
||||
}
|
||||
return animationSet;
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ export class RootLayout extends GridLayout {
|
||||
bringToFront(view: View, animated?: boolean): Promise<void>;
|
||||
closeAll(): Promise<void>;
|
||||
getShadeCover(): View;
|
||||
openShadeCover(options: ShadeCoverOptions): void;
|
||||
closeShadeCover(shadeCoverOptions?: ShadeCoverOptions): Promise<void>;
|
||||
}
|
||||
|
||||
export function getRootLayout(): RootLayout;
|
||||
|
@ -2,9 +2,16 @@ import { Color } from '../../../color';
|
||||
import { View } from '../../core/view';
|
||||
import { RootLayoutBase, defaultShadeCoverOptions } from './root-layout-common';
|
||||
import { TransitionAnimation, ShadeCoverOptions } from '.';
|
||||
import { LinearGradient } from '../../styling/linear-gradient';
|
||||
import { ios as iosViewUtils } from '../../utils';
|
||||
import { parseLinearGradient } from '../../../css/parser';
|
||||
export * from './root-layout-common';
|
||||
|
||||
export class RootLayout extends RootLayoutBase {
|
||||
// perf optimization: only create and insert gradients if settings change
|
||||
private _currentGradient: string;
|
||||
private _gradientLayer: CAGradientLayer;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@ -27,12 +34,24 @@ export class RootLayout extends RootLayoutBase {
|
||||
...defaultShadeCoverOptions,
|
||||
...shadeOptions,
|
||||
};
|
||||
if (view && view.nativeViewProtected) {
|
||||
if (view?.nativeViewProtected) {
|
||||
const duration = this._convertDurationToSeconds(options.animation?.enterFrom?.duration || defaultShadeCoverOptions.animation.enterFrom.duration);
|
||||
|
||||
if (options.color && options.color.startsWith('linear-gradient')) {
|
||||
if (options.color !== this._currentGradient) {
|
||||
this._currentGradient = options.color;
|
||||
const parsedGradient = parseLinearGradient(options.color);
|
||||
this._gradientLayer = iosViewUtils.drawGradient(view.nativeViewProtected, LinearGradient.parse(parsedGradient.value), 0);
|
||||
}
|
||||
}
|
||||
UIView.animateWithDurationAnimationsCompletion(
|
||||
duration,
|
||||
() => {
|
||||
view.nativeViewProtected.backgroundColor = new Color(options.color).ios;
|
||||
if (this._gradientLayer) {
|
||||
this._gradientLayer.opacity = 1;
|
||||
} else if (options.color && view?.nativeViewProtected) {
|
||||
view.nativeViewProtected.backgroundColor = new Color(options.color).ios;
|
||||
}
|
||||
this._applyAnimationProperties(view, {
|
||||
translateX: 0,
|
||||
translateY: 0,
|
||||
@ -71,14 +90,21 @@ export class RootLayout extends RootLayoutBase {
|
||||
});
|
||||
}
|
||||
|
||||
protected _cleanupPlatformShadeCover(): void {
|
||||
this._currentGradient = null;
|
||||
this._gradientLayer = null;
|
||||
}
|
||||
|
||||
private _applyAnimationProperties(view: View, shadeCoverAnimation: TransitionAnimation): void {
|
||||
const translate = CGAffineTransformMakeTranslation(shadeCoverAnimation.translateX, shadeCoverAnimation.translateY);
|
||||
// ios doesn't like scale being 0, default it to a small number greater than 0
|
||||
const scale = CGAffineTransformMakeScale(shadeCoverAnimation.scaleX || 0.1, shadeCoverAnimation.scaleY || 0.1);
|
||||
const rotate = CGAffineTransformMakeRotation((shadeCoverAnimation.rotate * Math.PI) / 180); // convert degress to radians
|
||||
const translateAndScale = CGAffineTransformConcat(translate, scale);
|
||||
view.nativeViewProtected.transform = CGAffineTransformConcat(rotate, translateAndScale);
|
||||
view.nativeViewProtected.alpha = shadeCoverAnimation.opacity;
|
||||
if (view?.nativeViewProtected) {
|
||||
const translate = CGAffineTransformMakeTranslation(shadeCoverAnimation.translateX, shadeCoverAnimation.translateY);
|
||||
// ios doesn't like scale being 0, default it to a small number greater than 0
|
||||
const scale = CGAffineTransformMakeScale(shadeCoverAnimation.scaleX || 0.1, shadeCoverAnimation.scaleY || 0.1);
|
||||
const rotate = CGAffineTransformMakeRotation((shadeCoverAnimation.rotate * Math.PI) / 180); // convert degress to radians
|
||||
const translateAndScale = CGAffineTransformConcat(translate, scale);
|
||||
view.nativeViewProtected.transform = CGAffineTransformConcat(rotate, translateAndScale);
|
||||
view.nativeViewProtected.alpha = shadeCoverAnimation.opacity;
|
||||
}
|
||||
}
|
||||
|
||||
private _convertDurationToSeconds(duration: number): number {
|
||||
|
@ -4,6 +4,7 @@ import { CSSType, View } from '../../core/view';
|
||||
import { GridLayout } from '../grid-layout';
|
||||
import { RootLayout, RootLayoutOptions, ShadeCoverOptions, TransitionAnimation } from '.';
|
||||
import { Animation } from '../../animation';
|
||||
import { AnimationDefinition } from '../../animation';
|
||||
|
||||
@CSSType('RootLayout')
|
||||
export class RootLayoutBase extends GridLayout {
|
||||
@ -33,16 +34,15 @@ export class RootLayoutBase extends GridLayout {
|
||||
// keep track of the views locally to be able to use their options later
|
||||
this.popupViews.push({ view: view, options: options });
|
||||
|
||||
// only insert 1 layer of shade cover (don't insert another one if already present)
|
||||
if (options?.shadeCover && !this.shadeCover) {
|
||||
this.shadeCover = this.createShadeCover(options.shadeCover);
|
||||
// insert shade cover at index right above the first layout
|
||||
this.insertChild(this.shadeCover, this.staticChildCount + 1);
|
||||
}
|
||||
|
||||
// overwrite current shadeCover options if topmost popupview has additional shadeCover configurations
|
||||
else if (options?.shadeCover && this.shadeCover) {
|
||||
this.updateShadeCover(this.shadeCover, options.shadeCover);
|
||||
if (options?.shadeCover) {
|
||||
// perf optimization note: we only need 1 layer of shade cover
|
||||
// we just update properties if needed by additional overlaid views
|
||||
if (this.shadeCover) {
|
||||
// overwrite current shadeCover options if topmost popupview has additional shadeCover configurations
|
||||
this.updateShadeCover(this.shadeCover, options.shadeCover);
|
||||
} else {
|
||||
this.openShadeCover(options.shadeCover);
|
||||
}
|
||||
}
|
||||
|
||||
view.opacity = 0; // always begin with view invisible when adding dynamically
|
||||
@ -77,47 +77,46 @@ export class RootLayoutBase extends GridLayout {
|
||||
close(view: View, exitTo?: TransitionAnimation): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.hasChild(view)) {
|
||||
const cleanupAndFinish = () => {
|
||||
this.removeChild(view);
|
||||
resolve();
|
||||
};
|
||||
|
||||
try {
|
||||
const popupIndex = this.getPopupIndex(view);
|
||||
// use exitAnimation that is passed in and fallback to the exitAnimation passed in when opening
|
||||
const exitAnimationDefinition = exitTo || this.popupViews[popupIndex]?.options?.animation?.exitTo;
|
||||
|
||||
// Remove view from local array
|
||||
const poppedView = this.popupViews[popupIndex];
|
||||
// use exitAnimation that is passed in and fallback to the exitAnimation passed in when opening
|
||||
const exitAnimationDefinition = exitTo || poppedView?.options?.animation?.exitTo;
|
||||
|
||||
// Remove view from tracked popupviews
|
||||
this.popupViews.splice(popupIndex, 1);
|
||||
|
||||
// update shade cover with the topmost popupView options (if not specifically told to ignore)
|
||||
const shadeCoverOptions = this.popupViews[this.popupViews.length - 1]?.options?.shadeCover;
|
||||
if (this.shadeCover && shadeCoverOptions && !poppedView?.options?.shadeCover.ignoreShadeRestore) {
|
||||
this.updateShadeCover(this.shadeCover, shadeCoverOptions);
|
||||
if (this.shadeCover) {
|
||||
// update shade cover with the topmost popupView options (if not specifically told to ignore)
|
||||
if (!poppedView?.options?.shadeCover.ignoreShadeRestore) {
|
||||
const shadeCoverOptions = this.popupViews[this.popupViews.length - 1]?.options?.shadeCover;
|
||||
if (shadeCoverOptions) {
|
||||
this.updateShadeCover(this.shadeCover, shadeCoverOptions);
|
||||
}
|
||||
}
|
||||
// remove shade cover animation if this is the last opened popup view
|
||||
if (this.popupViews.length === 0) {
|
||||
this.closeShadeCover(poppedView.options.shadeCover);
|
||||
}
|
||||
}
|
||||
|
||||
if (exitAnimationDefinition) {
|
||||
const exitAnimation = this.getExitAnimation(view, exitAnimationDefinition);
|
||||
const exitAnimations: Promise<any>[] = [exitAnimation.play()];
|
||||
|
||||
// add remove shade cover animation if this is the last opened popup view
|
||||
if (this.popupViews.length === 0 && this.shadeCover) {
|
||||
exitAnimations.push(this.closeShadeCover(poppedView.options.shadeCover));
|
||||
}
|
||||
return Promise.all(exitAnimations)
|
||||
.then(() => {
|
||||
this.removeChild(view);
|
||||
resolve();
|
||||
})
|
||||
this.getExitAnimation(view, exitAnimationDefinition)
|
||||
.play()
|
||||
.then(cleanupAndFinish.bind(this))
|
||||
.catch((ex) => {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Error playing exit animation: ${ex}`, Trace.categories.Layout, Trace.messageType.error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cleanupAndFinish();
|
||||
}
|
||||
this.removeChild(view);
|
||||
|
||||
// also remove shade cover if this is the last opened popup view
|
||||
if (this.popupViews.length === 0) {
|
||||
this.closeShadeCover(poppedView.options.shadeCover);
|
||||
}
|
||||
resolve();
|
||||
} catch (ex) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Error closing popup (${view}): ${ex}`, Trace.categories.Layout, Trace.messageType.error);
|
||||
@ -147,6 +146,44 @@ export class RootLayoutBase extends GridLayout {
|
||||
});
|
||||
}
|
||||
|
||||
getShadeCover(): View {
|
||||
return this.shadeCover;
|
||||
}
|
||||
|
||||
openShadeCover(options: ShadeCoverOptions) {
|
||||
if (this.shadeCover) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`RootLayout shadeCover already open.`, Trace.categories.Layout, Trace.messageType.warn);
|
||||
}
|
||||
} else {
|
||||
// create the one and only shade cover
|
||||
this.shadeCover = this.createShadeCover(options);
|
||||
// insert shade cover at index right above the first layout
|
||||
this.insertChild(this.shadeCover, this.staticChildCount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
closeShadeCover(shadeCoverOptions?: ShadeCoverOptions): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
// if shade cover is displayed and the last popup is closed, also close the shade cover
|
||||
if (this.shadeCover) {
|
||||
return this._closeShadeCover(this.shadeCover, shadeCoverOptions).then(() => {
|
||||
if (this.shadeCover) {
|
||||
this.shadeCover.off('loaded');
|
||||
if (this.shadeCover.parent) {
|
||||
this.removeChild(this.shadeCover);
|
||||
}
|
||||
}
|
||||
this.shadeCover = null;
|
||||
// cleanup any platform specific details related to shade cover
|
||||
this._cleanupPlatformShadeCover();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
// bring any view instance open on the rootlayout to front of all the children visually
|
||||
bringToFront(view: View, animated: boolean = false): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -214,10 +251,6 @@ export class RootLayoutBase extends GridLayout {
|
||||
});
|
||||
}
|
||||
|
||||
getShadeCover(): View {
|
||||
return this.shadeCover;
|
||||
}
|
||||
|
||||
private getPopupIndex(view: View): number {
|
||||
return this.popupViews.findIndex((popupView) => popupView.view === view);
|
||||
}
|
||||
@ -287,21 +320,17 @@ export class RootLayoutBase extends GridLayout {
|
||||
}
|
||||
|
||||
private getExitAnimation(targetView: View, exitTo: TransitionAnimation): Animation {
|
||||
const animationOptions = {
|
||||
return new Animation([this.getExitAnimationDefinition(targetView, exitTo)]);
|
||||
}
|
||||
|
||||
private getExitAnimationDefinition(targetView: View, exitTo: TransitionAnimation): AnimationDefinition {
|
||||
return {
|
||||
target: targetView,
|
||||
...defaultTransitionAnimation,
|
||||
...exitTo,
|
||||
...(exitTo || {}),
|
||||
translate: { x: exitTo.translateX || defaultTransitionAnimation.translateX, y: exitTo.translateY || defaultTransitionAnimation.translateY },
|
||||
scale: { x: exitTo.scaleX || defaultTransitionAnimation.scaleX, y: exitTo.scaleY || defaultTransitionAnimation.scaleY },
|
||||
};
|
||||
return new Animation([
|
||||
{
|
||||
target: targetView,
|
||||
translate: { x: animationOptions.translateX, y: animationOptions.translateY },
|
||||
scale: { x: animationOptions.scaleX, y: animationOptions.scaleY },
|
||||
rotate: animationOptions.rotate,
|
||||
opacity: animationOptions.opacity,
|
||||
duration: animationOptions.duration,
|
||||
curve: animationOptions.curve,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
private createShadeCover(shadeOptions: ShadeCoverOptions): View {
|
||||
@ -330,21 +359,6 @@ export class RootLayoutBase extends GridLayout {
|
||||
return this.getChildIndex(view) >= 0;
|
||||
}
|
||||
|
||||
private closeShadeCover(shadeCoverOptions?: ShadeCoverOptions): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
// if shade cover is displayed and the last popup is closed, also close the shade cover
|
||||
if (this.shadeCover) {
|
||||
return this._closeShadeCover(this.shadeCover, shadeCoverOptions).then(() => {
|
||||
this.removeChild(this.shadeCover);
|
||||
this.shadeCover.off('loaded');
|
||||
this.shadeCover = null;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
protected _bringToFront(view: View) {}
|
||||
|
||||
protected _initShadeCover(view: View, shadeOption: ShadeCoverOptions): void {}
|
||||
@ -356,6 +370,8 @@ export class RootLayoutBase extends GridLayout {
|
||||
protected _closeShadeCover(view: View, shadeOptions: ShadeCoverOptions): Promise<void> {
|
||||
return new Promise(() => {});
|
||||
}
|
||||
|
||||
protected _cleanupPlatformShadeCover(): void {}
|
||||
}
|
||||
|
||||
export function getRootLayout(): RootLayout {
|
||||
|
@ -5,6 +5,7 @@ import { View, Point } from '../core/view';
|
||||
import { LinearGradient } from './linear-gradient';
|
||||
import { Color } from '../../color';
|
||||
import { iOSNativeHelper, isDataURI, isFileOrResourcePath, layout } from '../../utils';
|
||||
import { ios as iosViewUtils, NativeScriptUIView } from '../utils';
|
||||
import { ImageSource } from '../../image-source';
|
||||
import { CSSValue, parse as cssParse } from '../../css-value';
|
||||
import { CSSShadow } from './css-shadow';
|
||||
@ -13,23 +14,6 @@ import { BackgroundClearFlags } from './background-common';
|
||||
|
||||
export * from './background-common';
|
||||
|
||||
interface NativeView extends UIView {
|
||||
hasNonUniformBorder: boolean;
|
||||
|
||||
borderLayer: CALayer;
|
||||
|
||||
hasBorderMask: boolean;
|
||||
borderOriginalMask: CALayer;
|
||||
|
||||
topBorderLayer: CALayer;
|
||||
rightBorderLayer: CALayer;
|
||||
bottomBorderLayer: CALayer;
|
||||
leftBorderLayer: CALayer;
|
||||
|
||||
gradientLayer: CAGradientLayer;
|
||||
boxShadowLayer: CALayer;
|
||||
}
|
||||
|
||||
interface Rect {
|
||||
left: number;
|
||||
top: number;
|
||||
@ -47,7 +31,7 @@ export enum CacheMode {
|
||||
export namespace ios {
|
||||
export function createBackgroundUIColor(view: View, callback: (uiColor: UIColor) => void, flip?: boolean): void {
|
||||
const background = view.style.backgroundInternal;
|
||||
const nativeView = <NativeView>view.nativeViewProtected;
|
||||
const nativeView = <NativeScriptUIView>view.nativeViewProtected;
|
||||
|
||||
if (background.clearFlags & BackgroundClearFlags.CLEAR_BOX_SHADOW) {
|
||||
// clear box shadow if it has been removed!
|
||||
@ -60,9 +44,9 @@ export namespace ios {
|
||||
clearNonUniformBorders(nativeView);
|
||||
}
|
||||
|
||||
clearGradient(nativeView);
|
||||
iosViewUtils.clearGradient(nativeView);
|
||||
if (background.image instanceof LinearGradient) {
|
||||
drawGradient(nativeView, background.image);
|
||||
iosViewUtils.drawGradient(nativeView, background.image);
|
||||
}
|
||||
|
||||
const hasNonUniformBorderWidths = background.hasBorderWidth() && !background.hasUniformBorder();
|
||||
@ -113,7 +97,7 @@ function onScroll(this: void, args: ScrollEventData): void {
|
||||
}
|
||||
}
|
||||
|
||||
function adjustLayersForScrollView(nativeView: UIScrollView & NativeView) {
|
||||
function adjustLayersForScrollView(nativeView: UIScrollView & NativeScriptUIView) {
|
||||
const layer = nativeView.borderLayer;
|
||||
if (layer instanceof CALayer) {
|
||||
// Compensates with transition for the background layers for scrolling in ScrollView based controls.
|
||||
@ -149,7 +133,7 @@ function subscribeForScrollNotifications(view: View) {
|
||||
}
|
||||
}
|
||||
|
||||
function clearNonUniformBorders(nativeView: NativeView): void {
|
||||
function clearNonUniformBorders(nativeView: NativeScriptUIView): void {
|
||||
if (nativeView.borderLayer) {
|
||||
nativeView.borderLayer.removeFromSuperlayer();
|
||||
}
|
||||
@ -459,7 +443,7 @@ function cssValueToDeviceIndependentPixels(source: string, total: number): numbe
|
||||
}
|
||||
}
|
||||
|
||||
function drawUniformColorNonUniformBorders(nativeView: NativeView, background: BackgroundDefinition) {
|
||||
function drawUniformColorNonUniformBorders(nativeView: NativeScriptUIView, background: BackgroundDefinition) {
|
||||
const layer = nativeView.layer;
|
||||
layer.backgroundColor = undefined;
|
||||
layer.borderColor = undefined;
|
||||
@ -587,7 +571,7 @@ function drawUniformColorNonUniformBorders(nativeView: NativeView, background: B
|
||||
nativeView.hasNonUniformBorder = true;
|
||||
}
|
||||
|
||||
function drawNoRadiusNonUniformBorders(nativeView: NativeView, background: BackgroundDefinition) {
|
||||
function drawNoRadiusNonUniformBorders(nativeView: NativeScriptUIView, background: BackgroundDefinition) {
|
||||
const borderLayer = CALayer.layer();
|
||||
nativeView.layer.addSublayer(borderLayer);
|
||||
nativeView.borderLayer = borderLayer;
|
||||
@ -726,7 +710,7 @@ 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) {
|
||||
function drawBoxShadow(nativeView: NativeScriptUIView, view: View, boxShadow: CSSShadow, background: BackgroundDefinition, useSubLayer: boolean = false) {
|
||||
const layer: CALayer = iOSNativeHelper.getShadowLayer(nativeView, 'ns-box-shadow');
|
||||
|
||||
layer.masksToBounds = false;
|
||||
@ -765,7 +749,7 @@ function drawBoxShadow(nativeView: NativeView, view: View, boxShadow: CSSShadow,
|
||||
layer.shadowPath = UIBezierPath.bezierPathWithRoundedRectCornerRadius(bounds, cornerRadius).CGPath;
|
||||
}
|
||||
|
||||
function clearBoxShadow(nativeView: NativeView) {
|
||||
function clearBoxShadow(nativeView: NativeScriptUIView) {
|
||||
nativeView.clipsToBounds = true;
|
||||
const layer: CALayer = iOSNativeHelper.getShadowLayer(nativeView, 'ns-box-shadow', false);
|
||||
if (!layer) {
|
||||
@ -779,46 +763,6 @@ function clearBoxShadow(nativeView: NativeView) {
|
||||
layer.shadowOpacity = 0.0;
|
||||
}
|
||||
|
||||
function drawGradient(nativeView: NativeView, gradient: LinearGradient) {
|
||||
const gradientLayer = CAGradientLayer.layer();
|
||||
gradientLayer.frame = nativeView.bounds;
|
||||
nativeView.gradientLayer = gradientLayer;
|
||||
|
||||
const iosColors = NSMutableArray.alloc().initWithCapacity(gradient.colorStops.length);
|
||||
const iosStops = NSMutableArray.alloc<number>().initWithCapacity(gradient.colorStops.length);
|
||||
let hasStops = false;
|
||||
|
||||
gradient.colorStops.forEach((stop) => {
|
||||
iosColors.addObject(stop.color.ios.CGColor);
|
||||
if (stop.offset) {
|
||||
iosStops.addObject(stop.offset.value);
|
||||
hasStops = true;
|
||||
}
|
||||
});
|
||||
|
||||
gradientLayer.colors = iosColors;
|
||||
|
||||
if (hasStops) {
|
||||
gradientLayer.locations = iosStops;
|
||||
}
|
||||
|
||||
const alpha = gradient.angle / (Math.PI * 2);
|
||||
const startX = Math.pow(Math.sin(Math.PI * (alpha + 0.75)), 2);
|
||||
const startY = Math.pow(Math.sin(Math.PI * (alpha + 0.5)), 2);
|
||||
const endX = Math.pow(Math.sin(Math.PI * (alpha + 0.25)), 2);
|
||||
const endY = Math.pow(Math.sin(Math.PI * alpha), 2);
|
||||
gradientLayer.startPoint = { x: startX, y: startY };
|
||||
gradientLayer.endPoint = { x: endX, y: endY };
|
||||
|
||||
nativeView.layer.insertSublayerAtIndex(gradientLayer, 0);
|
||||
}
|
||||
|
||||
function clearGradient(nativeView: NativeView): void {
|
||||
if (nativeView.gradientLayer) {
|
||||
nativeView.gradientLayer.removeFromSuperlayer();
|
||||
}
|
||||
}
|
||||
|
||||
function drawClipPath(nativeView: UIView, background: BackgroundDefinition) {
|
||||
const layer = nativeView.layer;
|
||||
const layerBounds = layer.bounds;
|
||||
|
17
packages/core/ui/styling/gradient.d.ts
vendored
17
packages/core/ui/styling/gradient.d.ts
vendored
@ -1,17 +0,0 @@
|
||||
import { CoreTypes } from '../../core-types';
|
||||
import { Color } from '../../color';
|
||||
import { LinearGradient as LinearGradientDefinition } from '../../css/parser';
|
||||
|
||||
export class LinearGradient {
|
||||
public angle: number;
|
||||
public colorStops: ColorStop[];
|
||||
|
||||
public static parse(value: LinearGradientDefinition): LinearGradientDefinition;
|
||||
|
||||
public static equals(first: LinearGradientDefinition, second: LinearGradientDefinition): boolean;
|
||||
}
|
||||
|
||||
export interface ColorStop {
|
||||
color: Color;
|
||||
offset?: CoreTypes.LengthPercentUnit;
|
||||
}
|
@ -1,8 +1,12 @@
|
||||
import { CoreTypes } from '../../core-types';
|
||||
import { Color } from '../../color';
|
||||
import { ColorStop } from './gradient';
|
||||
import { LinearGradient as CSSLinearGradient } from '../../css/parser';
|
||||
|
||||
export interface ColorStop {
|
||||
color: Color;
|
||||
offset?: CoreTypes.LengthPercentUnit;
|
||||
}
|
||||
|
||||
export class LinearGradient {
|
||||
public angle: number;
|
||||
public colorStops: ColorStop[];
|
||||
|
32
packages/core/ui/utils.d.ts
vendored
32
packages/core/ui/utils.d.ts
vendored
@ -1,4 +1,19 @@
|
||||
export namespace ios {
|
||||
export interface NativeScriptUIView extends UIView {
|
||||
hasNonUniformBorder: boolean;
|
||||
borderLayer: CALayer;
|
||||
|
||||
hasBorderMask: boolean;
|
||||
borderOriginalMask: CALayer;
|
||||
|
||||
topBorderLayer: CALayer;
|
||||
rightBorderLayer: CALayer;
|
||||
bottomBorderLayer: CALayer;
|
||||
leftBorderLayer: CALayer;
|
||||
|
||||
gradientLayer: CAGradientLayer;
|
||||
boxShadowLayer: CALayer;
|
||||
}
|
||||
export namespace ios {
|
||||
/**
|
||||
* Gets actual height of a [UIView](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/) widget in device pixels.
|
||||
* @param uiView - An instance of UIView.
|
||||
@ -10,4 +25,19 @@
|
||||
* @param viewController when specified it is used to check preferStatusBarHidden property.
|
||||
*/
|
||||
export function getStatusBarHeight(viewController?: any): number;
|
||||
|
||||
/**
|
||||
* draw gradient using CAGradientLayer and insert into UIView sublayer
|
||||
* @param nativeView UIView
|
||||
* @param gradient Parsed LinearGradient
|
||||
* @param gradientLayerOpacity Initial layer opacity (in case you'd like to use with animation sequence)
|
||||
* @param index sublayer index to insert layer at (defaults to 0)
|
||||
*/
|
||||
export function drawGradient(uiView: any /* UIView */, gradient: LinearGradient, gradientLayerOpacity?: number, index?: number): any; /* CAGradientLayer */
|
||||
|
||||
/**
|
||||
* clear gradientLayer if found on provided UIView
|
||||
* @param nativeView UIView
|
||||
*/
|
||||
export function clearGradient(uiView: any /* UIView */): void;
|
||||
}
|
||||
|
@ -1,4 +1,21 @@
|
||||
import * as utils from '../utils';
|
||||
import { LinearGradient } from './styling/linear-gradient';
|
||||
|
||||
interface NativeScriptUIView extends UIView {
|
||||
hasNonUniformBorder: boolean;
|
||||
borderLayer: CALayer;
|
||||
|
||||
hasBorderMask: boolean;
|
||||
borderOriginalMask: CALayer;
|
||||
|
||||
topBorderLayer: CALayer;
|
||||
rightBorderLayer: CALayer;
|
||||
bottomBorderLayer: CALayer;
|
||||
leftBorderLayer: CALayer;
|
||||
|
||||
gradientLayer: CAGradientLayer;
|
||||
boxShadowLayer: CALayer;
|
||||
}
|
||||
|
||||
export namespace ios {
|
||||
export function getActualHeight(view: UIView): number {
|
||||
@ -24,4 +41,51 @@ export namespace ios {
|
||||
|
||||
return utils.layout.toDevicePixels(min);
|
||||
}
|
||||
|
||||
export function drawGradient(nativeView: NativeScriptUIView, gradient: LinearGradient, gradientLayerOpacity?: number, index?: number): CAGradientLayer {
|
||||
let gradientLayer: CAGradientLayer;
|
||||
if (nativeView && gradient) {
|
||||
gradientLayer = CAGradientLayer.layer();
|
||||
if (typeof gradientLayerOpacity === 'number') {
|
||||
gradientLayer.opacity = gradientLayerOpacity;
|
||||
}
|
||||
gradientLayer.frame = nativeView.bounds;
|
||||
nativeView.gradientLayer = gradientLayer;
|
||||
|
||||
const iosColors = NSMutableArray.alloc().initWithCapacity(gradient.colorStops.length);
|
||||
const iosStops = NSMutableArray.alloc<number>().initWithCapacity(gradient.colorStops.length);
|
||||
let hasStops = false;
|
||||
|
||||
gradient.colorStops.forEach((stop) => {
|
||||
iosColors.addObject(stop.color.ios.CGColor);
|
||||
if (stop.offset) {
|
||||
iosStops.addObject(stop.offset.value);
|
||||
hasStops = true;
|
||||
}
|
||||
});
|
||||
|
||||
gradientLayer.colors = iosColors;
|
||||
|
||||
if (hasStops) {
|
||||
gradientLayer.locations = iosStops;
|
||||
}
|
||||
|
||||
const alpha = gradient.angle / (Math.PI * 2);
|
||||
const startX = Math.pow(Math.sin(Math.PI * (alpha + 0.75)), 2);
|
||||
const startY = Math.pow(Math.sin(Math.PI * (alpha + 0.5)), 2);
|
||||
const endX = Math.pow(Math.sin(Math.PI * (alpha + 0.25)), 2);
|
||||
const endY = Math.pow(Math.sin(Math.PI * alpha), 2);
|
||||
gradientLayer.startPoint = { x: startX, y: startY };
|
||||
gradientLayer.endPoint = { x: endX, y: endY };
|
||||
|
||||
nativeView.layer.insertSublayerAtIndex(gradientLayer, index || 0);
|
||||
}
|
||||
return gradientLayer;
|
||||
}
|
||||
|
||||
export function clearGradient(nativeView: NativeScriptUIView): void {
|
||||
if (nativeView?.gradientLayer) {
|
||||
nativeView.gradientLayer.removeFromSuperlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user