Content-Length: 644351 | pFad | http://github.com/NativeScript/NativeScript/pull/10684/files

2D feat(core): Added flexibility for using multiple RootLayouts by CatchABus · Pull Request #10684 · NativeScript/NativeScript · GitHub
Skip to content

feat(core): Added flexibility for using multiple RootLayouts #10684

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/ui/layouts/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export { AbsoluteLayout } from './absolute-layout';
export { DockLayout } from './dock-layout';
export { FlexboxLayout } from './flexbox-layout';
export { GridLayout, GridUnitType, ItemSpec } from './grid-layout';
export { RootLayout, getRootLayout, RootLayoutOptions, ShadeCoverOptions } from './root-layout';
export { RootLayout, getRootLayout, getRootLayoutById, RootLayoutOptions, ShadeCoverOptions } from './root-layout';
export { StackLayout } from './stack-layout';
export { WrapLayout } from './wrap-layout';
export { LayoutBase } from './layout-base';
2 changes: 1 addition & 1 deletion packages/core/ui/layouts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export { AbsoluteLayout } from './absolute-layout';
export { DockLayout } from './dock-layout';
export { FlexboxLayout } from './flexbox-layout';
export { GridLayout, GridUnitType, ItemSpec } from './grid-layout';
export { RootLayout, getRootLayout } from './root-layout';
export { RootLayout, getRootLayout, getRootLayoutById } from './root-layout';
export type { RootLayoutOptions, ShadeCoverOptions } from './root-layout';
export { StackLayout } from './stack-layout';
export { WrapLayout } from './wrap-layout';
Expand Down
4 changes: 0 additions & 4 deletions packages/core/ui/layouts/root-layout/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ import { LinearGradient } from '../../styling/linear-gradient';
export * from './root-layout-common';

export class RootLayout extends RootLayoutBase {
constructor() {
super();
}

insertChild(view: View, atIndex: number): void {
super.insertChild(view, atIndex);
if (!view.hasGestureObservers()) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/ui/layouts/root-layout/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class RootLayout extends GridLayout {
}

export function getRootLayout(): RootLayout;
export function getRootLayoutById(id: string): RootLayout;

export interface RootLayoutOptions {
shadeCover?: ShadeCoverOptions;
Expand Down
18 changes: 13 additions & 5 deletions packages/core/ui/layouts/root-layout/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ import { parseLinearGradient } from '../../../css/parser';
export * from './root-layout-common';

export class RootLayout extends RootLayoutBase {
nativeViewProtected: UIView;

// perf optimization: only create and insert gradients if settings change
private _currentGradient: string;
private _gradientLayer: CAGradientLayer;

constructor() {
super();
public disposeNativeView(): void {
super.disposeNativeView();
this._cleanupPlatformShadeCover();
}

protected _bringToFront(view: View) {
(<UIView>this.nativeViewProtected).bringSubviewToFront(view.nativeViewProtected);
this.nativeViewProtected.bringSubviewToFront(view.nativeViewProtected);
}

protected _initShadeCover(view: View, shadeOptions: ShadeCoverOptions): void {
Expand Down Expand Up @@ -46,7 +49,11 @@ export class RootLayout extends RootLayoutBase {
iosViewUtils.drawGradient(view.nativeViewProtected, this._gradientLayer, LinearGradient.parse(parsedGradient.value));
view.nativeViewProtected.layer.insertSublayerAtIndex(this._gradientLayer, 0);
}
} else {
// Dispose gradient if new color is null or a plain color
this._cleanupPlatformShadeCover();
}

UIView.animateWithDurationAnimationsCompletion(
duration,
() => {
Expand All @@ -66,7 +73,7 @@ export class RootLayout extends RootLayoutBase {
},
(completed: boolean) => {
resolve();
}
},
);
}
});
Expand All @@ -87,14 +94,15 @@ export class RootLayout extends RootLayoutBase {
},
(completed: boolean) => {
resolve();
}
},
);
}
});
}

protected _cleanupPlatformShadeCover(): void {
this._currentGradient = null;

if (this._gradientLayer != null) {
this._gradientLayer.removeFromSuperlayer();
this._gradientLayer = null;
Expand Down
137 changes: 83 additions & 54 deletions packages/core/ui/layouts/root-layout/root-layout-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,29 @@ import { RootLayout, RootLayoutOptions, ShadeCoverOptions, TransitionAnimation }
import { Animation } from '../../animation';
import { AnimationDefinition } from '../../animation';
import { isNumber } from '../../../utils/types';
import { _findRootLayoutById, _pushIntoRootLayoutStack, _removeFromRootLayoutStack, _geRootLayoutFromStack } from './root-layout-stack';

@CSSType('RootLayout')
export class RootLayoutBase extends GridLayout {
private shadeCover: View;
private staticChildCount: number;
private popupViews: { view: View; options: RootLayoutOptions }[] = [];
private _shadeCover: View;
private _popupViews: { view: View; options: RootLayoutOptions }[] = [];

constructor() {
super();
global.rootLayout = this;
public initNativeView(): void {
super.initNativeView();

_pushIntoRootLayoutStack(this);
}

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

super.onLoaded();
_removeFromRootLayoutStack(this);
}

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

if (this.popupViews.length > 0) {
if (this._popupViews.length > 0) {
this.closeAll();
handled = true;
}
Expand All @@ -55,29 +54,32 @@ export class RootLayoutBase extends GridLayout {
}

if (this.hasChild(view)) {
return reject(new Error(`${view} has already been added`));
return reject(new Error(`View ${view} has already been added to the root layout`));
}

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

// keep track of the views locally to be able to use their options later
this.popupViews.push({ view: view, options: options });
// Keep track of the views locally to be able to use their options later
this._popupViews.push({ view: view, options: options });

// Always begin with view invisible when adding dynamically
view.opacity = 0;
// Add view to view tree before adding shade cover
// Before being added to view tree, shade cover calculates the index to be inserted based on existing popup views
this.insertChild(view, this.getChildrenCount());

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) {
if (this._shadeCover) {
// overwrite current shadeCover options if topmost popupview has additional shadeCover configurations
toOpen.push(this.updateShadeCover(this.shadeCover, options.shadeCover));
toOpen.push(this.updateShadeCover(this._shadeCover, options.shadeCover));
} else {
toOpen.push(this.openShadeCover(options.shadeCover));
}
}

view.opacity = 0; // always begin with view invisible when adding dynamically
this.insertChild(view, this.getChildrenCount() + 1);

toOpen.push(
new Promise<void>((res, rej) => {
setTimeout(() => {
Expand Down Expand Up @@ -125,12 +127,12 @@ export class RootLayoutBase extends GridLayout {
}

if (!this.hasChild(view)) {
return reject(new Error(`Unable to close popup. ${view} not found`));
return reject(new Error(`Unable to close popup. View ${view} not found`));
}

const toClose = [];
const popupIndex = this.getPopupIndex(view);
const poppedView = this.popupViews[popupIndex];
const poppedView = this._popupViews[popupIndex];
const cleanupAndFinish = () => {
view.notify({ eventName: 'closed', object: view });
this.removeChild(view);
Expand All @@ -141,7 +143,7 @@ export class RootLayoutBase extends GridLayout {

// Remove view from tracked popupviews
if (popupIndex > -1) {
this.popupViews.splice(popupIndex, 1);
this._popupViews.splice(popupIndex, 1);
}

toClose.push(
Expand All @@ -158,13 +160,13 @@ export class RootLayoutBase extends GridLayout {
}),
);

if (this.shadeCover) {
if (this._shadeCover) {
// Update shade cover with the topmost popupView options (if not specifically told to ignore)
if (this.popupViews.length) {
if (this._popupViews.length) {
if (!poppedView?.options?.shadeCover?.ignoreShadeRestore) {
const shadeCoverOptions = this.popupViews[this.popupViews.length - 1].options?.shadeCover;
const shadeCoverOptions = this._popupViews[this._popupViews.length - 1].options?.shadeCover;
if (shadeCoverOptions) {
toClose.push(this.updateShadeCover(this.shadeCover, shadeCoverOptions));
toClose.push(this.updateShadeCover(this._shadeCover, shadeCoverOptions));
}
}
} else {
Expand All @@ -186,7 +188,7 @@ export class RootLayoutBase extends GridLayout {

closeAll(): Promise<void[]> {
const toClose = [];
const views = this.popupViews.map((popupView) => popupView.view);
const views = this._popupViews.map((popupView) => popupView.view);

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

getShadeCover(): View {
return this.shadeCover;
return this._shadeCover;
}

openShadeCover(options: ShadeCoverOptions = {}): Promise<void> {
return new Promise((resolve) => {
if (this.shadeCover) {
const childrenCount = this.getChildrenCount();

let indexToAdd: number;

if (this._popupViews.length) {
const { view } = this._popupViews[0];
const index = this.getChildIndex(view);

indexToAdd = index > -1 ? index : childrenCount;
} else {
indexToAdd = childrenCount;
}

if (this._shadeCover) {
if (Trace.isEnabled()) {
Trace.write(`RootLayout shadeCover already open.`, Trace.categories.Layout, Trace.messageType.warn);
}
Expand All @@ -216,25 +231,25 @@ export class RootLayoutBase extends GridLayout {
});
});

this.shadeCover = shadeCover;
// Insert shade cover at index right above the first layout
this.insertChild(this.shadeCover, this.staticChildCount + 1);
this._shadeCover = shadeCover;
// Insert shade cover at index right below the first popup view
this.insertChild(this._shadeCover, indexToAdd);
}
});
}

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);
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;
this._shadeCover = null;
// cleanup any platform specific details related to shade cover
this._cleanupPlatformShadeCover();
resolve();
Expand All @@ -245,30 +260,40 @@ export class RootLayoutBase extends GridLayout {
}

topmost(): View {
return this.popupViews.length ? this.popupViews[this.popupViews.length - 1].view : null;
return this._popupViews.length ? this._popupViews[this._popupViews.length - 1].view : null;
}

// bring any view instance open on the rootlayout to front of all the children visually
/**
* This method causes the requested view to overlap its siblings by bring it to front.
*
* @param view
* @param animated
* @returns
*/
bringToFront(view: View, animated: boolean = false): Promise<void> {
return new Promise((resolve, reject) => {
if (!(view instanceof View)) {
return reject(new Error(`Invalid bringToFront view: ${view}`));
}

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

const popupIndex = this.getPopupIndex(view);
// popupview should be present and not already the topmost view
if (popupIndex < 0 || popupIndex == this.popupViews.length - 1) {
return reject(new Error(`${view} not found or already at topmost`));

if (popupIndex < 0) {
return reject(new Error(`View ${view} is not a child of the root layout`));
}

if (popupIndex == this._popupViews.length - 1) {
return reject(new Error(`View ${view} is already the topmost view in the rootlayout`));
}

// keep the popupViews array in sync with the stacking of the views
const currentView = this.popupViews[popupIndex];
this.popupViews.splice(popupIndex, 1);
this.popupViews.push(currentView);
const currentView = this._popupViews[popupIndex];
this._popupViews.splice(popupIndex, 1);
this._popupViews.push(currentView);

const exitAnimation = this.getViewExitState(view);
if (animated && exitAnimation) {
Expand Down Expand Up @@ -302,22 +327,22 @@ export class RootLayoutBase extends GridLayout {
// update shadeCover to reflect topmost's shadeCover options
const shadeCoverOptions = currentView?.options?.shadeCover;
if (shadeCoverOptions) {
this.updateShadeCover(this.shadeCover, shadeCoverOptions);
this.updateShadeCover(this._shadeCover, shadeCoverOptions);
}
resolve();
});
}

private getPopupIndex(view: View): number {
return this.popupViews.findIndex((popupView) => popupView.view === view);
return this._popupViews.findIndex((popupView) => popupView.view === view);
}

private getViewInitialState(view: View): TransitionAnimation {
const popupIndex = this.getPopupIndex(view);
if (popupIndex === -1) {
return;
}
const initialState = this.popupViews[popupIndex]?.options?.animation?.enterFrom;
const initialState = this._popupViews[popupIndex]?.options?.animation?.enterFrom;
if (!initialState) {
return;
}
Expand All @@ -329,7 +354,7 @@ export class RootLayoutBase extends GridLayout {
if (popupIndex === -1) {
return;
}
const exitAnimation = this.popupViews[popupIndex]?.options?.animation?.exitTo;
const exitAnimation = this._popupViews[popupIndex]?.options?.animation?.exitTo;
if (!exitAnimation) {
return;
}
Expand Down Expand Up @@ -428,7 +453,11 @@ export class RootLayoutBase extends GridLayout {
}

export function getRootLayout(): RootLayout {
return <RootLayout>global.rootLayout;
return _geRootLayoutFromStack(0);
}

export function getRootLayoutById(id: string): RootLayout {
return _findRootLayoutById(id);
}

export const defaultTransitionAnimation: TransitionAnimation = {
Expand Down
Loading
Loading








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/NativeScript/NativeScript/pull/10684/files

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy