8000 feat(root-layout): support gradient colors on shade cover · NativeScript/NativeScript@f55e62d · GitHub
[go: up one dir, main page]

Skip to content

Commit f55e62d

Browse files
committed
feat(root-layout): support gradient colors on shade cover
1 parent a3823ff commit f55e62d

File tree

9 files changed

+232
-163
lines changed

9 files changed

+232
-163
lines changed

packages/core/ui/core/view/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { EventData } from '../../../data/observable';
44
import { Color } from '../../../color';
55
import { Animation, AnimationDefinition, AnimationPromise } from '../../animation';
66
import { GestureTypes, GesturesObserver } from '../../gestures';
7-
import { LinearGradient } from '../../styling/gradient';
7+
import { LinearGradient } from '../../styling/linear-gradient';
88
import { AccessibilityLiveRegion, AccessibilityRole, AccessibilityState, AccessibilityTrait, AccessibilityEventOptions } from '../../../accessibility/accessibility-types';
99
import { CoreTypes } from '../../../core-types';
1010
import { CSSShadow } from '../../styling/css-shadow';

packages/core/ui/layouts/root-layout/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export class RootLayout extends GridLayout {
77
bringToFront(view: View, animated?: boolean): Promise<void>;
88
closeAll(): Promise<void>;
99
getShadeCover(): View;
10+
openShadeCover(options: ShadeCoverOptions): void;
11+
closeShadeCover(shadeCoverOptions?: ShadeCoverOptions): Promise<void>;
1012
}
1113

1214
export function getRootLayout(): RootLayout;

packages/core/ui/layouts/root-layout/index.ios.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@ import { Color } from '../../../color';
22
import { View } from '../../core/view';
33
import { RootLayoutBase, defaultShadeCoverOptions } from './root-layout-common';
44
import { TransitionAnimation, ShadeCoverOptions } from '.';
5+
import { LinearGradient } from '../../styling/linear-gradient';
6+
import { ios as iosViewUtils } from '../../utils';
7+
import { parseLinearGradient } from '../../../css/parser';
58
export * from './root-layout-common';
69

710
export class RootLayout extends RootLayoutBase {
11+
// perf optimization: only create and insert gradients if settings change
12+
private _currentGradient: string;
13+
private _gradientLayer: CAGradientLayer;
14+
815
constructor() {
916
super();
1017
}
@@ -27,12 +34,24 @@ export class RootLayout extends RootLayoutBase {
2734
...defaultShadeCoverOptions,
2835
...shadeOptions,
2936
};
30-
if (view && view.nativeViewProtected) {
37+
if (view?.nativeViewProtected) {
3138
const duration = this._convertDurationToSeconds(options.animation?.enterFrom?.duration || defaultShadeCoverOptions.animation.enterFrom.duration);
39+
40+
if (options.color && options.color.startsWith('linear-gradient')) {
41+
if (options.color !== this._currentGradient) {
42+
this._currentGradient = options.color;
43+
const parsedGradient = parseLinearGradient(options.color);
44+
this._gradientLayer = iosViewUtils.drawGradient(view.nativeViewProtected, LinearGradient.parse(parsedGradient.value), 0);
45+
}
46+
}
3247
UIView.animateWithDurationAnimationsCompletion(
3348
duration,
3449
() => {
35-
view.nativeViewProtected.backgroundColor = new Color(options.color).ios;
50+
if (this._gradientLayer) {
51+
this._gradientLayer.opacity = 1;
52+
} else if (options.color && view?.nativeViewProtected) {
53+
view.nativeViewProtected.backgroundColor = new Color(options.color).ios;
54+
}
3655
this._applyAnimationProperties(view, {
3756
translateX: 0,
3857
translateY: 0,
@@ -71,14 +90,21 @@ export class RootLayout extends RootLayoutBase {
7190
});
7291
}
7392

93+
protected _cleanupPlatformShadeCover(): void {
94+
this._currentGradient = null;
95+
this._gradientLayer = null;
96+
}
97+
7498
private _applyAnimationProperties(view: View, shadeCoverAnimation: TransitionAnimation): void {
75-
const translate = CGAffineTransformMakeTranslation(shadeCoverAnimation.translateX, shadeCoverAnimation.translateY);
76-
// ios doesn't like scale being 0, default it to a small number greater than 0
77-
const scale = CGAffineTransformMakeScale(shadeCoverAnimation.scaleX || 0.1, shadeCoverAnimation.scaleY || 0.1);
78-
const rotate = CGAffineTransformMakeRotation((shadeCoverAnimation.rotate * Math.PI) / 180); // convert degress to radians
79-
const translateAndScale = CGAffineTransformConcat(translate, scale);
80-
view.nativeViewProtected.transform = CGAffineTransformConcat(rotate, translateAndScale);
81-
view.nativeViewProtected.alpha = shadeCoverAnimation.opacity;
99+
if (view?.nativeViewProtected) {
100+
const translate = CGAffineTransformMakeTranslation(shadeCoverAnimation.translateX, shadeCoverAnimation.translateY);
101+
// ios doesn't like scale being 0, default it to a small number greater than 0
102+
const scale = CGAffineTransformMakeScale(shadeCoverAnimation.scaleX || 0.1, shadeCoverAnimation.scaleY || 0.1);
103+
const rotate = CGAffineTransformMakeRotation((shadeCoverAnimation.rotate * Math.PI) / 180); // convert degress to radians
104+
const translateAndScale = CGAffineTransformConcat(translate, scale);
105+
view.nativeViewProtected.transform = CGAffineTransformConcat(rotate, translateAndScale);
106+
view.nativeViewProtected.alpha = shadeCoverAnimation.opacity;
107+
}
82108
}
83109

84110
private _convertDurationToSeconds(duration: number): number {

packages/core/ui/layouts/root-layout/root-layout-common.ts

Lines changed: 84 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { CSSType, View } from '../../core/view';
44
import { GridLayout } from '../grid-layout';
55
import { RootLayout, RootLayoutOptions, ShadeCoverOptions, TransitionAnimation } from '.';
66
import { Animation } from '../../animation';
7+
import { AnimationDefinition } from '../../animation';
78

89
@CSSType('RootLayout')
910
export class RootLayoutBase extends GridLayout {
@@ -33,16 +34,15 @@ export class RootLayoutBase extends GridLayout {
3334
// keep track of the views locally to be able to use their options later
3435
this.popupViews.push({ view: view, options: options });
3536

36-
// only insert 1 layer of shade cover (don't insert another one if already present)
37-
if (options?.shadeCover && !this.shadeCover) {
38-
this.shadeCover = this.createShadeCover(options.shadeCover);
39-
// insert shade cover at index right above the first layout
40-
this.insertChild(this.shadeCover, this.staticChildCount + 1);
41-
}
42-
43-
// overwrite current shadeCover options if topmost popupview has additional shadeCover configurations
44-
else if (options?.shadeCover && this.shadeCover) {
45-
this.updateShadeCover(this.shadeCover, options.shadeCover);
37+
if (options?.shadeCover) {
38+
// perf optimization note: we only need 1 layer of shade cover
39+
// we just update properties if needed by additional overlaid views
40+
if (this.shadeCover) {
41+
// overwrite current shadeCover options if topmost popupview has additional shadeCover configurations
42+
this.updateShadeCover(this.shadeCover, options.shadeCover);
43+
} else {
44+
this.openShadeCover(options.shadeCover);
45+
}
4646
}
4747

4848
view.opacity = 0; // always begin with view invisible when adding dynamically
@@ -77,47 +77,46 @@ export class RootLayoutBase extends GridLayout {
7777
close(view: View, exitTo?: TransitionAnimation): Promise<void> {
7878
return new Promise((resolve, reject) => {
7979
if (this.hasChild(view)) {
80+
const cleanupAndFinish = () => {
81+
this.removeChild(view);
82+
resolve();
83+
};
84+
8085
try {
8186
const popupIndex = this.getPopupIndex(view);
87+
const poppedView = this.popupViews[popupIndex];
8288
// use exitAnimation that is passed in and fallback to the exitAnimation passed in when opening
83-
const exitAnimationDefinition = exitTo || this.popupViews[popupIndex]?.options?.animation?.exitTo;
89+
const exitAnimationDefinition = exitTo || poppedView?.options?.animation?.exitTo;
8490

85-
// Remove view from local array
86-
const poppedView = this.popupViews[popupIndex];
91+
// Remove view from tracked popupviews
8792
this.popupViews.splice(popupIndex, 1);
8893

89-
// update shade cover with the topmost popupView options (if not specifically told to ignore)
90-
const shadeCoverOptions = this.popupViews[this.popupViews.length - 1]?.options?.shadeCover;
91-
if (this.shadeCover && shadeCoverOptions && !poppedView?.options?.shadeCover.ignoreShadeRestore) {
92-
this.updateShadeCover(this.shadeCover, shadeCoverOptions);
94+
if (this.shadeCover) {
95+
// update shade cover with the topmost popupView options (if not specifically told to ignore)
96+
if (!poppedView?.options?.shadeCover.ignoreShadeRestore) {
97+
const shadeCoverOptions = this.popupViews[this.popupViews.length - 1]?.options?.shadeCover;
98+
if (shadeCoverOptions) {
99+
this.updateShadeCover(this.shadeCover, shadeCoverOptions);
100+
}
101+
}
102+
// remove shade cover animation if this is the last opened popup view
103+
if (this.popupViews.length === 0) {
104+
this.closeShadeCover(poppedView.options.shadeCover);
105+
}
93106
}
94107

95108
if (exitAnimationDefinition) {
96-
const exitAnimation = this.getExitAnimation(view, exitAnimationDefinition);
97-
const exitAnimations: Promise<any>[] = [exitAnimation.play()];
98-
99-
// add remove shade cover animation if this is the last opened popup view
100-
if (this.popupViews.length === 0 && this.shadeCover) {
101-
exitAnimations.push(this.closeShadeCover(poppedView.options.shadeCover));
102-
}
103-
return Promise.all(exitAnimations)
104-
.then(() => {
105-
this.removeChild(view);
106-
resolve();
107-
})
109+
this.getExitAnimation(view, exitAnimationDefinition)
110+
.play()
111+
.then(cleanupAndFinish.bind(this))
108112
.catch((ex) => {
109113
if (Trace.isEnabled()) {
110114
Trace.write(`Error playing exit animation: ${ex}`, Trace.categories.Layout, Trace.messageType.error);
111115
}
112116
});
117+
} else {
118+
cleanupAndFinish();
113119
}
114-
this.removeChild(view);
115-
116-
// also remove shade cover if this is the last opened popup view
117-
if (this.popupViews.length === 0) {
118-
this.closeShadeCover(poppedView.options.shadeCover);
119-
}
120-
resolve();
121120
} catch (ex) {
122121
if (Trace.isEnabled()) {
123122
Trace.write(`Error closing popup (${view}): ${ex}`, Trace.categories.Layout, Trace.messageType.error);
@@ -147,6 +146,44 @@ export class RootLayoutBase extends GridLayout {
147146
});
148147
}
149148

149+
getShadeCover(): View {
150+
return this.shadeCover;
151+
}
152+
153+
openShadeCover(options: ShadeCoverOptions) {
154+
if (this.shadeCover) {
155+
if (Trace.isEnabled()) {
156+
Trace.write(`RootLayout shadeCover already open.`, Trace.categories.Layout, Trace.messageType.warn);
157+
}
158+
} else {
159+
// create the one and only shade cover
160+
this.shadeCover = this.createShadeCover(options);
161+
// insert shade cover at index right above the first layout
162+
this.insertChild(this.shadeCover, this.staticChildCount + 1);
163+
}
164+
}
165+
166+
closeShadeCover(shadeCoverOptions?: ShadeCoverOptions): Promise<void> {
167+
return new Promise((resolve) => {
168+
// if shade cover is displayed and the last popup is closed, also close the shade cover
169+
if (this.shadeCover) {
170+
return this._closeShadeCover(this.shadeCover, shadeCoverOptions).then(() => {
171+
if (this.shadeCover) {
172+
this.shadeCover.off('loaded');
173+
if (this.shadeCover.parent) {
174+
this.removeChild(this.shadeCover);
175+
}
176+
}
177+
this.shadeCover = null;
178+
// cleanup any platform specific details related to shade cover
179+
this._cleanupPlatformShadeCover();
180+
resolve();
181+
});
182+
}
183+
resolve();
184+
});
185+
}
186+
150187
// bring any view instance open on the rootlayout to front of all the children visually
151188
bringToFront(view: View, animated: boolean = false): Promise<void> {
152189
return new Promise((resolve, reject) => {
@@ -214,10 +251,6 @@ export class RootLayoutBase extends GridLayout {
214251
});
215252
}
216253

217-
getShadeCover(): View {
218-
return this.shadeCover;
219-
}
220-
221254
private getPopupIndex(view: View): number {
222255
return this.popupViews.findIndex((popupView) => popupView.view === view);
223256
}
@@ -287,21 +320,17 @@ export class RootLayoutBase extends GridLayout {
287320
}
288321

289322
private getExitAnimation(targetView: View, exitTo: TransitionAnimation): Animation {
290-
const animationOptions = {
323+
return new Animation([this.getExitAnimationDefinition(targetView, exitTo)]);
324+
}
325+
326+
private getExitAnimationDefinition(targetView: View, exitTo: TransitionAnimation): AnimationDefinition {
327+
return {
328+
target: targetView,
291329
...defaultTransitionAnimation,
292-
...exitTo,
330+
...(exitTo || {}),
331+
translate: { x: exitTo.translateX || defaultTransitionAnimation.translateX, y: exitTo.translateY || defaultTransitionAnimation.translateY },
332+
scale: { x: exitTo.scaleX || defaultTransitionAnimation.scaleX, y: exitTo.scaleY || defaultTransitionAnimation.scaleY },
293333
};
294-
return new Animation([
295-
{
296-
target: targetView,
297-
translate: { x: animationOptions.translateX, y: animationOptions.translateY },
298-
scale: { x: animationOptions.scaleX, y: animationOptions.scaleY },
299-
rotate: animationOptions.rotate,
300-
opacity: animationOptions.opacity,
301-
duration: animationOptions.duration,
302-
curve: animationOptions.curve,
303-
},
304-
]);
305334
}
306335

307336
private createShadeCover(shadeOptions: ShadeCoverOptions): View {
@@ -330,21 +359,6 @@ export class RootLayoutBase extends GridLayout {
330359
return this.getChildIndex(view) >= 0;
331360
}
332361

333-
private closeShadeCover(shadeCoverOptions?: ShadeCoverOptions): Promise<void> {
334-
return new Promise((resolve) => {
335-
// if shade cover is displayed and the last popup is closed, also close the shade cover
336-
if (this.shadeCover) {
337-
return this._closeShadeCover(this.shadeCover, shadeCoverOptions).then(() => {
338-
this.removeChild(this.shadeCover);
339-
this.shadeCover.off('loaded');
340-
this.shadeCover = null;
341-
resolve();
342-
});
343-
}
344-
resolve();
345-
});
346-
}
347-
348362
protected _bringToFront(view: View) {}
349363

350364
protected _initShadeCover(view: View, shadeOption: ShadeCoverOptions): void {}
@@ -356,6 +370,8 @@ export class RootLayoutBase extends GridLayout {
356370
protected _closeShadeCover(view: View, shadeOptions: ShadeCoverOptions): Promise<void> {
357371
return new Promise(() => {});
358372
}
373+
374+
protected _cleanupPlatformShadeCover(): void {}
359375
}
360376

361377
export function getRootLayout(): RootLayout {

0 commit comments

Comments
 (0)
0