8000 feat(core): flexibility using multiple RootLayouts (#10684) · NativeScript/NativeScript@4b87a35 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4b87a35

Browse files
authored
feat(core): flexibility using multiple RootLayouts (#10684)
1 parent 79a0306 commit 4b87a35

File tree

7 files changed

+124
-65
lines changed

7 files changed

+124
-65
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export { AbsoluteLayout } from './absolute-layout';
22
export { DockLayout } from './dock-layout';
33
export { FlexboxLayout } from './flexbox-layout';
44
export { GridLayout, GridUnitType, ItemSpec } from './grid-layout';
5-
export { RootLayout, getRootLayout, RootLayoutOptions, ShadeCoverOptions } from './root-layout';
5+
export { RootLayout, getRootLayout, getRootLayoutById, RootLayoutOptions, ShadeCoverOptions } from './root-layout';
66
export { StackLayout } from './stack-layout';
77
export { WrapLayout } from './wrap-layout';
88
export { LayoutBase } from './layout-base';

packages/core/ui/layouts/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export { AbsoluteLayout } from './absolute-layout';
22
export { DockLayout } from './dock-layout';
33
export { FlexboxLayout } from './flexbox-layout';
44
export { GridLayout, GridUnitType, ItemSpec } from './grid-layout';
5-
export { RootLayout, getRootLayout } from './root-layout';
5+
export { RootLayout, getRootLayout, getRootLayoutById } from './root-layout';
66
export type { RootLayoutOptions, ShadeCoverOptions } from './root-layout';
77
export { StackLayout } from './stack-layout';
88
export { WrapLayout } from './wrap-layout';

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ import { LinearGradient } from '../../styling/linear-gradient';
88
export * from './root-layout-common';
99

1010
export class RootLayout extends RootLayoutBase {
11-
constructor() {
12-
super();
13-
}
14-
1511
insertChild(view: View, atIndex: number): void {
1612
super.insertChild(view, atIndex);
1713
if (!view.hasGestureObservers()) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export class RootLayout extends GridLayout {
1616
}
1717

1818
export function getRootLayout(): RootLayout;
19+
export function getRootLayoutById(id: string): RootLayout;
1920

2021
export interface RootLayoutOptions {
2122
shadeCover?: ShadeCoverOptions;

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@ import { parseLinearGradient } from '../../../css/parser';
88
export * from './root-layout-common';
99

1010
export class RootLayout extends RootLayoutBase {
11+
nativeViewProtected: UIView;
12+
1113
// perf optimization: only create and insert gradients if settings change
1214
private _currentGradient: string;
1315
private _gradientLayer: CAGradientLayer;
1416

15-
constructor() {
16-
super();
17+
public disposeNativeView(): void {
18+
super.disposeNativeView();
19+
this._cleanupPlatformShadeCover();
1720
}
1821

1922
protected _bringToFront(view: View) {
20-
(<UIView>this.nativeViewProtected).bringSubviewToFront(view.nativeViewProtected);
23+
this.nativeViewProtected.bringSubviewToFront(view.nativeViewProtected);
2124
}
2225

2326
protected _initShadeCover(view: View, shadeOptions: ShadeCoverOptions): void {
@@ -46,7 +49,11 @@ export class RootLayout extends RootLayoutBase {
4649
iosViewUtils.drawGradient(view.nativeViewProtected, this._gradientLayer, LinearGradient.parse(parsedGradient.value));
4750
view.nativeViewProtected.layer.insertSublayerAtIndex(this._gradientLayer, 0);
4851
}
52+
} else {
53+
// Dispose gradient if new color is null or a plain color
54+
this._cleanupPlatformShadeCover();
4955
}
56+
5057
UIView.animateWithDurationAnimationsCompletion(
5158
duration,
5259
() => {
@@ -66,7 +73,7 @@ export class RootLayout extends RootLayoutBase {
6673
},
6774
(completed: boolean) => {
6875
resolve();
69-
}
76+
},
7077
);
7178
}
7279
});
@@ -87,14 +94,15 @@ export class RootLayout extends RootLayoutBase {
8794
},
8895
(completed: boolean) => {
8996
resolve();
90-
}
97+
},
9198
);
9299
}
93100
});
94101
}
95102

96103
protected _cleanupPlatformShadeCover(): void {
97104
this._currentGradient = null;
105+
98106
if (this._gradientLayer != null) {
99107
this._gradientLayer.removeFromSuperlayer();
100108
this._gradientLayer = null;

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

Lines changed: 83 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,29 @@ import { RootLayout, RootLayoutOptions, ShadeCoverOptions, TransitionAnimation }
66
import { Animation } from '../../animation';
77
import { AnimationDefinition } from '../../animation';
88
import { isNumber } from '../../../utils/types';
9+
import { _findRootLayoutById, _pushIntoRootLayoutStack, _removeFromRootLayoutStack, _geRootLayoutFromStack } from './root-layout-stack';
910

1011
@CSSType('RootLayout')
1112
export class RootLayoutBase extends GridLayout {
12-
private shadeCover: View;
13-
private staticChildCount: number;
14-
private popupViews: { view: View; options: RootLayoutOptions }[] = [];
13+
private _shadeCover: View;
14+
private _popupViews: { view: View; options: RootLayoutOptions }[] = [];
1515

16-
constructor() {
17-
super();
18-
global.rootLayout = this;
16+
public initNativeView(): void {
17+
super.initNativeView();
18+
19+
_pushIntoRootLayoutStack(this);
1920
}
2021

21-
public onLoaded() {
22-
// get actual content count of rootLayout (elements between the <RootLayout> tags in the template).
23-
// All popups will be inserted dynamically at a higher index
24-
this.staticChildCount = this.getChildrenCount();
22+
public disposeNativeView(): void {
23+
super.disposeNativeView();
2524

26-
super.onLoaded();
25+
_removeFromRootLayoutStack(this);
2726
}
2827

2928
public _onLivesync(context?: ModuleContext): boolean {
3029
let handled = false;
3130

32-
if (this.popupViews.length > 0) {
31+
if (this._popupViews.length > 0) {
3332
this.closeAll();
3433
handled = true;
3534
}
@@ -55,29 +54,32 @@ export class RootLayoutBase extends GridLayout {
5554
}
5655

5756
if (this.hasChild(view)) {
58-
return reject(new Error(`${view} has already been added`));
57+
return reject(new Error(`View ${view} has already been added to the root layout`));
5958
}
6059

6160
const toOpen = [];
6261
const enterAnimationDefinition = options.animation ? options.animation.enterFrom : null;
6362

64-
// keep track of the views locally to be able to use their options later
65-
this.popupViews.push({ view: view, options: options });
63+
// Keep track of the views locally to be able to use their options later
64+
this._popupViews.push({ view: view, options: options });
65+
66+
// Always begin with view invisible when adding dynamically
67+
view.opacity = 0;
68+
// Add view to view tree before adding shade cover
69+
// Before being added to view tree, shade cover calculates the index to be inserted based on existing popup views
70+
this.insertChild(view, this.getChildrenCount());
6671

6772
if (options.shadeCover) {
6873
// perf optimization note: we only need 1 layer of shade cover
6974
// we just update properties if needed by additional overlaid views
70-
if (this.shadeCover) {
75+
if (this._shadeCover) {
7176
// overwrite current shadeCover options if topmost popupview has additional shadeCover configurations
72-
toOpen.push(this.updateShadeCover(this.shadeCover, options.shadeCover));
77+
toOpen.push(this.updateShadeCover(this._shadeCover, options.shadeCover));
7378
} else {
7479
toOpen.push(this.openShadeCover(options.shadeCover));
7580
}
7681
}
7782

78-
view.opacity = 0; // always begin with view invisible when adding dynamically
79-
this.insertChild(view, this.getChildrenCount() + 1);
80-
8183
toOpen.push(
8284
new Promise<void>((res, rej) => {
8385
setTimeout(() => {
@@ -125,12 +127,12 @@ export class RootLayoutBase extends GridLayout {
125127
}
126128

127129
if (!this.hasChild(view)) {
128-
return reject(new Error(`Unable to close popup. ${view} not found`));
130+
return reject(new Error(`Unable to close popup. View ${view} not found`));
129131
}
130132

131133
const toClose = [];
132134
const popupIndex = this.getPopupIndex(view);
133-
const poppedView = this.popupViews[popupIndex];
135+
const poppedView = this._popupViews[popupIndex];
134136
const cleanupAndFinish = () => {
135137
view.notify({ eventName: 'closed', object: view });
136138
this.removeChild(view);
@@ -141,7 +143,7 @@ export class RootLayoutBase extends GridLayout {
141143

142144
// Remove view from tracked popupviews
143145
if (popupIndex > -1) {
144-
this.popupViews.splice(popupIndex, 1);
146+
this._popupViews.splice(popupIndex, 1);
145147
}
146148

147149
toClose.push(
@@ -158,13 +160,13 @@ export class RootLayoutBase extends GridLayout {
158160
}),
159161
);
160162

161-
if (this.shadeCover) {
163+
if (this._shadeCover) {
162164
// Update shade cover with the topmost popupView options (if not specifically told to ignore)
163-
if (this.popupViews.length) {
165+
if (this._popupViews.length) {
164166
if (!poppedView?.options?.shadeCover?.ignoreShadeRestore) {
165-
const shadeCoverOptions = this.popupViews[this.popupViews.length - 1].options?.shadeCover;
167+
const shadeCoverOptions = this._popupViews[this._popupViews.length - 1].options?.shadeCover;
166168
if (shadeCoverOptions) {
167-
toClose.push(this.updateShadeCover(this.shadeCover, shadeCoverOptions));
169+
toClose.push(this.updateShadeCover(this._shadeCover, shadeCoverOptions));
168170
}
169171
}
170172
} else {
@@ -186,7 +188,7 @@ export class RootLayoutBase extends GridLayout {
186188

187189
closeAll(): Promise<void[]> {
188190
const toClose = [];
189-
const views = this.popupViews.map((popupView) => popupView.view);
191+
const views = this._popupViews.map((popupView) => popupView.view);
190192

191193
// Close all views at the same time and wait for all of them
192194
for (const view of views) {
@@ -196,12 +198,25 @@ export class RootLayoutBase extends GridLayout {
196198
}
197199

198200
getShadeCover(): View {
199-
return this.shadeCover;
201+
return this._shadeCover;
200202
}
201203

202204
openShadeCover(options: ShadeCoverOptions = {}): Promise<void> {
203205
return new Promise((resolve) => {
204-
if (this.shadeCover) {
206+
const childrenCount = this.getChildrenCount();
207+
208+
let indexToAdd: number;
209+
210+
if (this._popupViews.length) {
211+
const { view } = this._popupViews[0];
212+
const index = this.getChildIndex(view);
213+
214+
indexToAdd = index > -1 ? index : childrenCount;
215+
} else {
216+
indexToAdd = childrenCount;
217+
}
218+
219+
if (this._shadeCover) {
205220
if (Trace.isEnabled()) {
206221
Trace.write(`RootLayout shadeCover already open.`, Trace.categories.Layout, Trace.messageType.warn);
207222
}
@@ -216,25 +231,25 @@ export class RootLayoutBase extends GridLayout {
216231
});
217232
});
218233

219-
this.shadeCover = shadeCover;
220-
// Insert shade cover at index right above the first layout
221-
this.insertChild(this.shadeCover, this.staticChildCount + 1);
234+
this._shadeCover = shadeCover;
235+
// Insert shade cover at index right below the first popup view
236+
this.insertChild(this._shadeCover, indexToAdd);
222237
}
223238
});
224239
}
225240

226241
closeShadeCover(shadeCoverOptions: ShadeCoverOptions = {}): Promise<void> {
227242
return new Promise((resolve) => {
228243
// if shade cover is displayed and the last popup is closed, also close the shade cover
229-
if (this.shadeCover) {
230-
return this._closeShadeCover(this.shadeCover, shadeCoverOptions).then(() => {
231-
if (this.shadeCover) {
232-
this.shadeCover.off('loaded');
233-
if (this.shadeCover.parent) {
234-
this.removeChild(this.shadeCover);
244+
if (this._shadeCover) {
245+
return this._closeShadeCover(this._shadeCover, shadeCoverOptions).then(() => {
246+
if (this._shadeCover) {
247+
this._shadeCover.off('loaded');
248+
if (this._shadeCover.parent) {
249+
this.removeChild(this._shadeCover);
235250
}
236251
}
237-
this.shadeCover = null;
252+
this._shadeCover = null;
238253
// cleanup any platform specific details related to shade cover
239254
this._cleanupPlatformShadeCover();
240255
resolve();
@@ -245,30 +260,40 @@ export class RootLayoutBase extends GridLayout {
245260
}
246261

247262
topmost(): View {
248-
return this.popupViews.length ? this.popupViews[this.popupViews.length - 1].view : null;
263+
return this._popupViews.length ? this._popupViews[this._popupViews.length - 1].view : null;
249264
}
250265

251-
// bring any view instance open on the rootlayout to front of all the children visually
266+
/**
267+
* This method causes the requested view to overlap its siblings by bring it to front.
268+
*
269+
* @param view
270+
* @param animated
271+
* @returns
272+
*/
252273
bringToFront(view: View, animated: boolean = false): Promise<void> {
253274
return new Promise((resolve, reject) => {
254275
if (!(view instanceof View)) {
255276
return reject(new Error(`Invalid bringToFront view: ${view}`));
256277
}
257278

258279
if (!this.hasChild(view)) {
259-
return reject(new Error(`${view} not found or already at topmost`));
280+
return reject(new Error(`View ${view} is not a child of the root layout`));
260281
}
261282

262283
const popupIndex = this.getPopupIndex(view);
263-
// popupview should be present and not already the topmost view
264-
if (popupIndex < 0 || popupIndex == this.popupViews.length - 1) {
265-
return reject(new Error(`${view} not found or already at topmost`));
284+
285+
if (popupIndex < 0) {
286+
return reject(new Error(`View ${view} is not a child of the root layout`));
287+
}
288+
289+
if (popupIndex == this._popupViews.length - 1) {
290+
return reject(new Error(`View ${view} is already the topmost view in the rootlayout`));
266291
}
267292

268293
// keep the popupViews array in sync with the stacking of the views
269-
const currentView = this.popupViews[popupIndex];
270-
this.popupViews.splice(popupIndex, 1);
271-
this.popupViews.push(currentView);
294+< F438 /span>
const currentView = this._popupViews[popupIndex];
295+
this._popupViews.splice(popupIndex, 1);
296+
this._popupViews.push(currentView);
272297

273298
const exitAnimation = this.getViewExitState(view);
274299
if (animated && exitAnimation) {
@@ -302,22 +327,22 @@ export class RootLayoutBase extends GridLayout {
302327
// update shadeCover to reflect topmost's shadeCover options
303328
const shadeCoverOptions = currentView?.options?.shadeCover;
304329
if (shadeCoverOptions) {
305-
this.updateShadeCover(this.shadeCover, shadeCoverOptions);
330+
this.updateShadeCover(this._shadeCover, shadeCoverOptions);
306331
}
307332
resolve();
308333
});
309334
}
310335

311336
private getPopupIndex(view: View): number {
312-
return this.popupViews.findIndex((popupView) => popupView.view === view);
337+
return this._popupViews.findIndex((popupView) => popupView.view === view);
313338
}
314339

315340
private getViewInitialState(view: View): TransitionAnimation {
316341
const popupIndex = this.getPopupIndex(view);
317342
if (popupIndex === -1) {
318343
return;
319344
}
320-
const initialState = this.popupViews[popupIndex]?.options?.animation?.enterFrom;
345+
const initialState = this._popupViews[popupIndex]?.options?.animation?.enterFrom;
321346
if (!initialState) {
322347
return;
323348
}
@@ -329,7 +354,7 @@ export class RootLayoutBase extends GridLayout {
329354
if (popupIndex === -1) {
330355
return;
331356
}
332-
const exitAnimation = this.popupViews[popupIndex]?.options?.animation?.exitTo;
357+
const exitAnimation = this._popupViews[popupIndex]?.options?.animation?.exitTo;
333358
if (!exitAnimation) {
334359
return;
335360
}
@@ -428,7 +453,11 @@ export class RootLayoutBase extends GridLayout {
428453
}
429454

430455
export function getRootLayout(): RootLayout {
431-
return <RootLayout>global.rootLayout;
456+
return _geRootLayoutFromStack(0);
457+
}
458+
459+
export function getRootLayoutById(id: string): RootLayout {
460+
return _findRootLayoutById(id);
432461
}
433462

434463
export const defaultTransitionAnimation: TransitionAnimation = {

0 commit comments

Comments
 (0)
0