1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import display from '@ohos.display';
17import window from '@ohos.window';
18import hilog from '@ohos.hilog';
19import { LengthMetrics, Position, Size } from '@ohos.arkui.node';
20import curves from '@ohos.curves';
21import { Callback } from '@ohos.base';
22import mediaQuery from '@ohos.mediaquery';
23
24interface Layout {
25  size: Size;
26  position: Position;
27}
28
29interface RegionLayout {
30  primary: Layout;
31  secondary: Layout;
32  extra: Layout;
33}
34
35/**
36 * Position enum of the extra region
37 *
38 * @enum { number }
39 * @syscap SystemCapability.ArkUI.ArkUI.Full
40 * @crossplatform
41 * @atomicservice
42 * @since 12
43 */
44export enum ExtraRegionPosition {
45  /**
46   * The extra region position is in the top.
47   *
48   * @syscap SystemCapability.ArkUI.ArkUI.Full
49   * @crossplatform
50   * @atomicservice
51   * @since 12
52   */
53  TOP = 1,
54
55  /**
56   * The extra region position is in the bottom.
57   *
58   * @syscap SystemCapability.ArkUI.ArkUI.Full
59   * @crossplatform
60   * @atomicservice
61   * @since 12
62   */
63  BOTTOM = 2,
64}
65
66/**
67 * The layout options for the container when the foldable screen is expanded.
68 *
69 * @interface ExpandedRegionLayoutOptions
70 * @syscap SystemCapability.ArkUI.ArkUI.Full
71 * @crossplatform
72 * @atomicservice
73 * @since 12
74 */
75export interface ExpandedRegionLayoutOptions {
76  /**
77   * The ratio of the widths of two areas in the horizontal direction.
78   *
79   * @type { ?number }
80   * @syscap SystemCapability.ArkUI.ArkUI.Full
81   * @crossplatform
82   * @atomicservice
83   * @since 12
84   */
85  horizontalSplitRatio?: number;
86
87  /**
88   * The ratio of the heights of two areas in the vertical direction.
89   *
90   * @type { ?number }
91   * @syscap SystemCapability.ArkUI.ArkUI.Full
92   * @crossplatform
93   * @atomicservice
94   * @since 12
95   */
96  verticalSplitRatio?: number;
97
98  /**
99   * Does the extended area span from top to bottom within the container?
100   *
101   * @type { ?boolean }
102   * @syscap SystemCapability.ArkUI.ArkUI.Full
103   * @crossplatform
104   * @atomicservice
105   * @since 12
106   */
107  isExtraRegionPerpendicular?: boolean;
108
109  /**
110   * Specify the position of the extra area when the extra area does not vertically span the container.
111   *
112   * @type { ?ExtraRegionPosition }
113   * @syscap SystemCapability.ArkUI.ArkUI.Full
114   * @crossplatform
115   * @atomicservice
116   * @since 12
117   */
118  extraRegionPosition?: ExtraRegionPosition;
119}
120
121/**
122 * The layout options for the container when the foldable screen is in hover mode.
123 *
124 * @interface SemiFoldedRegionLayoutOptions
125 * @syscap SystemCapability.ArkUI.ArkUI.Full
126 * @crossplatform
127 * @atomicservice
128 * @since 12
129 */
130export interface HoverModeRegionLayoutOptions {
131  /**
132   * The ratio of the widths of two areas in the horizontal direction.
133   *
134   * @type { ?number }
135   * @syscap SystemCapability.ArkUI.ArkUI.Full
136   * @crossplatform
137   * @atomicservice
138   * @since 12
139   */
140  horizontalSplitRatio?: number;
141
142  /**
143   * Does the foldable screen display an extra area when it's in the half-folded state?
144   *
145   * @type { ?boolean }
146   * @syscap SystemCapability.ArkUI.ArkUI.Full
147   * @crossplatform
148   * @atomicservice
149   * @since 12
150   */
151  showExtraRegion?: boolean;
152
153  /**
154   * Specify the position of the extra area when the foldable screen is in the half-folded state.
155   *
156   * @type { ?ExtraRegionPosition }
157   * @syscap SystemCapability.ArkUI.ArkUI.Full
158   * @crossplatform
159   * @atomicservice
160   * @since 12
161   */
162  extraRegionPosition?: ExtraRegionPosition;
163}
164
165/**
166 * The layout options for the container when the foldable screen is folded.
167 *
168 * @interface FoldedRegionLayoutOptions
169 * @syscap SystemCapability.ArkUI.ArkUI.Full
170 * @crossplatform
171 * @atomicservice
172 * @since 12
173 */
174export interface FoldedRegionLayoutOptions {
175  /**
176   * The ratio of the heights of two areas in the vertical direction.
177   *
178   * @type { ?number }
179   * @syscap SystemCapability.ArkUI.ArkUI.Full
180   * @crossplatform
181   * @atomicservice
182   * @since 12
183   */
184  verticalSplitRatio?: number;
185}
186
187/**
188 * Preset split ratio.
189 *
190 * @enum { number }
191 * @syscap SystemCapability.ArkUI.ArkUI.Full
192 * @crossplatform
193 * @atomicservice
194 * @since 12
195 */
196export enum PresetSplitRatio {
197  /**
198   * 1:1
199   *
200   * @syscap SystemCapability.ArkUI.ArkUI.Full
201   * @crossplatform
202   * @atomicservice
203   * @since 12
204   */
205  LAYOUT_1V1 = 1 / 1,
206
207  /**
208   * 2:3
209   *
210   * @syscap SystemCapability.ArkUI.ArkUI.Full
211   * @crossplatform
212   * @atomicservice
213   * @since 12
214   */
215  LAYOUT_2V3 = 2 / 3,
216
217  /**
218   * 3:2
219   *
220   * @syscap SystemCapability.ArkUI.ArkUI.Full
221   * @crossplatform
222   * @atomicservice
223   * @since 12
224   */
225  LAYOUT_3V2 = 3 / 2,
226}
227
228/**
229 * The status of hover mode.
230 *
231 * @interface HoverStatus
232 * @syscap SystemCapability.ArkUI.ArkUI.Full
233 * @crossplatform
234 * @atomicservice
235 * @since 12
236 */
237export interface HoverModeStatus {
238  /**
239   * The fold status of devices.
240   *
241   * @type { display.FoldStatus }
242   * @syscap SystemCapability.ArkUI.ArkUI.Full
243   * @crossplatform
244   * @atomicservice
245   * @since 12
246   */
247  foldStatus: display.FoldStatus;
248
249  /**
250   * Is the app currently in hover mode?
251   * In hover mode, the upper half of the screen is used for display, and the lower half is used for operation.
252   *
253   * @type { boolean }
254   * @syscap SystemCapability.ArkUI.ArkUI.Full
255   * @crossplatform
256   * @atomicservice
257   * @since 12
258   */
259  isHoverMode: boolean;
260
261  /**
262   * The angle of rotation applied.
263   *
264   * @type { number }
265   * @syscap SystemCapability.ArkUI.ArkUI.Full
266   * @crossplatform
267   * @atomicservice
268   * @since 12
269   */
270  appRotation: number;
271
272  /**
273   * The status of window.
274   *
275   * @type { window.WindowStatusType }
276   * @syscap SystemCapability.ArkUI.ArkUI.Full
277   * @crossplatform
278   * @atomicservice
279   * @since 12
280   */
281  windowStatusType: window.WindowStatusType;
282}
283
284/**
285 * The handler of onHoverStatusChange event
286 *
287 * @typedef { function } OnHoverStatusChangeHandler
288 * @param { HoverModeStatus } status - The status of hover mode
289 * @syscap SystemCapability.ArkUI.ArkUI.Full
290 * @crossplatform
291 * @atomicservice
292 * @since 12
293 */
294export type OnHoverStatusChangeHandler = (status: HoverModeStatus) => void;
295
296function withDefaultValue<T>(value: T | undefined | null, defaultValue: T): T {
297  if (value === void 0 || value === null) {
298    return defaultValue;
299  }
300  return value;
301}
302
303function getSplitRatio(ratio: number | undefined | null, defaultRatio: number): number {
304  if (ratio === void 0 || ratio === null) {
305    return defaultRatio;
306  }
307  if (ratio <= 0) {
308    return defaultRatio;
309  }
310  return ratio;
311}
312
313class Logger {
314  static debug(format: string, ...args: ESObject[]): void {
315    return hilog.debug(0x3900, 'FoldSplitContainer', format, ...args);
316  }
317
318  static info(format: string, ...args: ESObject[]): void {
319    return hilog.info(0x3900, 'FoldSplitContainer', format, ...args);
320  }
321
322  static error(format: string, ...args: ESObject[]): void {
323    return hilog.error(0x3900, 'FoldSplitContainer', format, ...args);
324  }
325}
326
327function initLayout(): Layout {
328  return {
329    size: { width: 0, height: 0 },
330    position: { x: 0, y: 0 },
331  };
332}
333
334/**
335 * Defines FoldSplitContainer container.
336 *
337 * @interface FoldSplitContainer
338 * @syscap SystemCapability.ArkUI.ArkUI.Full
339 * @crossplatform
340 * @atomicservice
341 * @since 12
342 */
343@Component
344export struct FoldSplitContainer {
345  /**
346   * The builder function which will be rendered in the major region of container.
347   *
348   * @type { Callback<void> }
349   * @syscap SystemCapability.ArkUI.ArkUI.Full
350   * @crossplatform
351   * @atomicservice
352   * @since 12
353   */
354  @BuilderParam
355  primary: Callback<void>;
356  /**
357   * The builder function which will be rendered in the minor region of container.
358   *
359   * @type { Callback<void> }
360   * @syscap SystemCapability.ArkUI.ArkUI.Full
361   * @crossplatform
362   * @atomicservice
363   * @since 12
364   */
365  @BuilderParam
366  secondary: Callback<void>;
367  /**
368   * The builder function which will be rendered in the extra region of container.
369   *
370   * @type { ?Callback<void> }
371   * @syscap SystemCapability.ArkUI.ArkUI.Full
372   * @crossplatform
373   * @atomicservice
374   * @since 12
375   */
376  @BuilderParam
377  extra?: Callback<void>;
378  /**
379   * The layout options for the container when the foldable screen is expanded.
380   *
381   * @type { ExpandedRegionLayoutOptions }
382   * @syscap SystemCapability.ArkUI.ArkUI.Full
383   * @crossplatform
384   * @atomicservice
385   * @since 12
386   */
387  @Prop
388  @Watch('updateLayout')
389  expandedLayoutOptions: ExpandedRegionLayoutOptions = {
390    horizontalSplitRatio: PresetSplitRatio.LAYOUT_3V2,
391    verticalSplitRatio: PresetSplitRatio.LAYOUT_1V1,
392    isExtraRegionPerpendicular: true,
393    extraRegionPosition: ExtraRegionPosition.TOP
394  };
395  /**
396   * The layout options for the container when the foldable screen is in hover mode.
397   *
398   * @type { HoverModeRegionLayoutOptions  }
399   * @syscap SystemCapability.ArkUI.ArkUI.Full
400   * @crossplatform
401   * @atomicservice
402   * @since 12
403   */
404  @Prop
405  @Watch('updateLayout')
406  hoverModeLayoutOptions: HoverModeRegionLayoutOptions = {
407    horizontalSplitRatio: PresetSplitRatio.LAYOUT_3V2,
408    showExtraRegion: false,
409    extraRegionPosition: ExtraRegionPosition.TOP
410  };
411  /**
412   * The layout options for the container when the foldable screen is folded.
413   *
414   * @type { FoldedRegionLayoutOptions }
415   * @syscap SystemCapability.ArkUI.ArkUI.Full
416   * @crossplatform
417   * @atomicservice
418   * @since 12
419   */
420  @Prop
421  @Watch('updateLayout')
422  foldedLayoutOptions: FoldedRegionLayoutOptions = {
423    verticalSplitRatio: PresetSplitRatio.LAYOUT_1V1
424  };
425  /**
426   * The animation options of layout
427   *
428   * @type { AnimateParam | null }
429   * @syscap SystemCapability.ArkUI.ArkUI.Full
430   * @crossplatform
431   * @atomicservice
432   * @since 12
433   */
434  @Prop
435  animationOptions?: AnimateParam | null = undefined;
436  /**
437   * The callback function that is triggered when the foldable screen enters or exits hover mode.
438   * In hover mode, the upper half of the screen is used for display, and the lower half is used for operation.
439   *
440   * @type { ?OnHoverStatusChangeHandler }
441   * @syscap SystemCapability.ArkUI.ArkUI.Full
442   * @crossplatform
443   * @atomicservice
444   * @since 12
445   */
446  public onHoverStatusChange?: OnHoverStatusChangeHandler = () => {
447  };
448  @State primaryLayout: Layout = initLayout();
449  @State secondaryLayout: Layout = initLayout();
450  @State extraLayout: Layout = initLayout();
451  @State extraOpacity: number = 1;
452  private windowStatusType: window.WindowStatusType = window.WindowStatusType.UNDEFINED;
453  private foldStatus: display.FoldStatus = display.FoldStatus.FOLD_STATUS_UNKNOWN;
454  private windowInstance?: window.Window;
455  private containerSize: Size = { width: 0, height: 0 };
456  private containerGlobalPosition: Position = { x: 0, y: 0 };
457  private listener?: mediaQuery.MediaQueryListener;
458  private isSmallScreen: boolean = false;
459  private isHoverMode: boolean | undefined = undefined;
460
461  aboutToAppear() {
462    this.listener = mediaQuery.matchMediaSync('(width<=600vp)');
463    this.isSmallScreen = this.listener.matches;
464    this.listener.on('change', (result) => {
465      this.isSmallScreen = result.matches;
466    });
467
468    this.foldStatus = display.getFoldStatus();
469    display.on('foldStatusChange', (foldStatus) => {
470      if (this.foldStatus !== foldStatus) {
471        this.foldStatus = foldStatus;
472        this.updateLayout();
473        this.updatePreferredOrientation();
474      }
475    });
476
477    window.getLastWindow(this.getUIContext().getHostContext(), (error, windowInstance) => {
478      if (error && error.code) {
479        Logger.error('Failed to get window instance, error code: %{public}d', error.code);
480        return;
481      }
482
483      const windowId = windowInstance.getWindowProperties().id;
484      if (windowId < 0) {
485        Logger.error('Failed to get window instance because the window id is invalid. window id: %{public}d', windowId);
486        return;
487      }
488
489      this.windowInstance = windowInstance;
490      this.updatePreferredOrientation();
491      this.windowInstance.on('windowStatusChange', (status) => {
492        this.windowStatusType = status;
493      });
494    });
495  }
496
497  aboutToDisappear() {
498    if (this.listener) {
499      this.listener.off('change');
500      this.listener = undefined;
501    }
502    display.off('foldStatusChange');
503    if (this.windowInstance) {
504      this.windowInstance.off('windowStatusChange');
505    }
506  }
507
508  build() {
509    Stack() {
510      Column() {
511        if (this.primary) {
512          this.primary();
513        }
514      }
515      .size(this.primaryLayout.size)
516      .position({
517        start: LengthMetrics.vp(this.primaryLayout.position.x),
518        top: LengthMetrics.vp(this.primaryLayout.position.y),
519      })
520      .clip(true)
521
522      Column() {
523        if (this.secondary) {
524          this.secondary();
525        }
526      }
527      .size(this.secondaryLayout.size)
528      .position({
529        start: LengthMetrics.vp(this.secondaryLayout.position.x),
530        top: LengthMetrics.vp(this.secondaryLayout.position.y),
531      })
532      .clip(true)
533
534      if (this.extra) {
535        Column() {
536          this.extra?.();
537        }
538        .opacity(this.extraOpacity)
539        .animation({ curve: Curve.Linear, duration: 250 })
540        .size(this.extraLayout.size)
541        .position({
542          start: LengthMetrics.vp(this.extraLayout.position.x),
543          top: LengthMetrics.vp(this.extraLayout.position.y),
544        })
545        .clip(true)
546      }
547    }
548    .id('$$FoldSplitContainer$Stack$$')
549    .width('100%')
550    .height('100%')
551    .onSizeChange((_, size) => {
552      this.updateContainerSize(size);
553      this.updateContainerPosition();
554      this.updateLayout();
555    })
556  }
557
558  private dispatchHoverStatusChange(isHoverMode: boolean) {
559    if (this.onHoverStatusChange) {
560      this.onHoverStatusChange({
561        foldStatus: this.foldStatus,
562        isHoverMode: isHoverMode,
563        appRotation: display.getDefaultDisplaySync().rotation,
564        windowStatusType: this.windowStatusType,
565      });
566    }
567  }
568
569  private hasExtraRegion(): boolean {
570    return !!this.extra;
571  }
572
573  private async updatePreferredOrientation() {
574    if (this.windowInstance) {
575      try {
576        if (this.foldStatus === display.FoldStatus.FOLD_STATUS_FOLDED) {
577          await this.windowInstance.setPreferredOrientation(window.Orientation.AUTO_ROTATION_PORTRAIT);
578        } else {
579          await this.windowInstance.setPreferredOrientation(window.Orientation.AUTO_ROTATION);
580        }
581      } catch (err) {
582        Logger.error('Failed to update preferred orientation.');
583      }
584    }
585  }
586
587  private updateContainerSize(size: SizeOptions) {
588    this.containerSize.width = size.width as number;
589    this.containerSize.height = size.height as number;
590  }
591
592  private updateContainerPosition() {
593    const context = this.getUIContext();
594    const frameNode = context.getFrameNodeById('$$FoldSplitContainer$Stack$$');
595    if (frameNode) {
596      this.containerGlobalPosition = frameNode.getPositionToWindow();
597    }
598  }
599
600  private updateLayout() {
601    let isHoverMode: boolean = false;
602    let regionLayout: RegionLayout;
603    if (this.isSmallScreen) {
604      regionLayout = this.getFoldedRegionLayouts();
605    } else {
606      if (this.foldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED) {
607        regionLayout = this.getExpandedRegionLayouts();
608      } else if (this.foldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED) {
609        if (this.isPortraitOrientation()) {
610          regionLayout = this.getExpandedRegionLayouts();
611        } else {
612          regionLayout = this.getHoverModeRegionLayouts();
613          isHoverMode = true;
614        }
615      } else if (this.foldStatus === display.FoldStatus.FOLD_STATUS_FOLDED) {
616        regionLayout = this.getFoldedRegionLayouts();
617      } else {
618        regionLayout = this.getExpandedRegionLayouts();
619      }
620    }
621
622    if (this.animationOptions === null) {
623      this.primaryLayout = regionLayout.primary;
624      this.secondaryLayout = regionLayout.secondary;
625      this.extraLayout = regionLayout.extra;
626    } else if (this.animationOptions === void 0) {
627      animateTo({ curve: curves.springMotion(0.35, 1, 0) }, () => {
628        this.primaryLayout = regionLayout.primary;
629        this.secondaryLayout = regionLayout.secondary;
630        this.extraLayout = regionLayout.extra;
631      });
632    } else {
633      animateTo(this.animationOptions, () => {
634        this.primaryLayout = regionLayout.primary;
635        this.secondaryLayout = regionLayout.secondary;
636        this.extraLayout = regionLayout.extra;
637      });
638    }
639
640    if (this.isHoverMode !== isHoverMode) {
641      this.dispatchHoverStatusChange(isHoverMode);
642      this.isHoverMode = isHoverMode;
643    }
644
645    if (isHoverMode && !this.hoverModeLayoutOptions.showExtraRegion) {
646      this.extraOpacity = 0;
647    } else {
648      this.extraOpacity = 1;
649    }
650  }
651
652  private getExpandedRegionLayouts(): RegionLayout {
653    const width = this.containerSize.width;
654    const height = this.containerSize.height;
655    const primaryLayout: Layout = initLayout();
656    const secondaryLayout: Layout = initLayout();
657    const extraLayout: Layout = initLayout();
658
659    const horizontalSplitRatio =
660      getSplitRatio(this.expandedLayoutOptions.horizontalSplitRatio, PresetSplitRatio.LAYOUT_3V2);
661    const verticalSplitRatio =
662      getSplitRatio(this.expandedLayoutOptions.verticalSplitRatio, PresetSplitRatio.LAYOUT_1V1);
663
664    if (this.hasExtraRegion()) {
665      extraLayout.size.width = width / (horizontalSplitRatio + 1);
666    } else {
667      extraLayout.size.width = 0;
668    }
669    secondaryLayout.size.height = height / (verticalSplitRatio + 1);
670    primaryLayout.size.height = height - secondaryLayout.size.height;
671    primaryLayout.position.x = 0;
672    secondaryLayout.position.x = 0;
673    primaryLayout.position.y = 0;
674    secondaryLayout.position.y = primaryLayout.size.height;
675
676    const isExtraRegionPerpendicular = withDefaultValue(this.expandedLayoutOptions.isExtraRegionPerpendicular, true);
677    if (isExtraRegionPerpendicular) {
678      primaryLayout.size.width = width - extraLayout.size.width;
679      secondaryLayout.size.width = width - extraLayout.size.width;
680      extraLayout.size.height = height;
681      extraLayout.position.x = primaryLayout.size.width;
682      extraLayout.position.y = 0;
683    } else {
684      const extraRegionPosition =
685        withDefaultValue(this.expandedLayoutOptions.extraRegionPosition, ExtraRegionPosition.TOP);
686      if (extraRegionPosition === ExtraRegionPosition.BOTTOM) {
687        primaryLayout.size.width = width;
688        secondaryLayout.size.width = width - extraLayout.size.width;
689        extraLayout.size.height = secondaryLayout.size.height;
690        extraLayout.position.x = secondaryLayout.size.width;
691        extraLayout.position.y = primaryLayout.size.height;
692      } else {
693        primaryLayout.size.width = width - extraLayout.size.width;
694        secondaryLayout.size.width = width;
695        extraLayout.size.height = primaryLayout.size.height;
696        extraLayout.position.x = primaryLayout.size.width;
697        extraLayout.position.y = 0;
698      }
699    }
700
701    return { primary: primaryLayout, secondary: secondaryLayout, extra: extraLayout };
702  }
703
704  private getHoverModeRegionLayouts(): RegionLayout {
705    const width = this.containerSize.width;
706    const height = this.containerSize.height;
707    const primaryLayout: Layout = initLayout();
708    const secondaryLayout: Layout = initLayout();
709    const extraLayout: Layout = initLayout();
710    const creaseRegionRect = this.getCreaseRegionRect();
711    primaryLayout.position.x = 0;
712    primaryLayout.position.y = 0;
713    secondaryLayout.position.x = 0;
714    secondaryLayout.position.y = creaseRegionRect.top + creaseRegionRect.height;
715    secondaryLayout.size.height = height - secondaryLayout.position.y;
716    primaryLayout.size.height = creaseRegionRect.top;
717
718    const showExtraRegion = withDefaultValue(this.hoverModeLayoutOptions.showExtraRegion, false);
719
720    if (!showExtraRegion) {
721      primaryLayout.size.width = width;
722      secondaryLayout.size.width = width;
723      extraLayout.position.x = width;
724      const isExpandedExtraRegionPerpendicular =
725        withDefaultValue(this.expandedLayoutOptions.isExtraRegionPerpendicular, true);
726      if (isExpandedExtraRegionPerpendicular) {
727        extraLayout.size.height = this.extraLayout.size.height;
728      } else {
729        const expandedExtraRegionPosition =
730          withDefaultValue(this.expandedLayoutOptions.extraRegionPosition, ExtraRegionPosition.TOP);
731        if (expandedExtraRegionPosition === ExtraRegionPosition.BOTTOM) {
732          extraLayout.size.height = secondaryLayout.size.height;
733          extraLayout.position.y = secondaryLayout.position.y;
734        } else {
735          extraLayout.size.height = primaryLayout.size.height;
736          extraLayout.position.y = 0;
737        }
738      }
739    } else {
740      const horizontalSplitRatio =
741        getSplitRatio(this.hoverModeLayoutOptions.horizontalSplitRatio, PresetSplitRatio.LAYOUT_3V2);
742      const extraRegionPosition =
743        withDefaultValue(this.hoverModeLayoutOptions.extraRegionPosition, ExtraRegionPosition.TOP);
744      if (this.hasExtraRegion()) {
745        extraLayout.size.width = width / (horizontalSplitRatio + 1);
746      } else {
747        extraLayout.size.width = 0;
748      }
749      if (extraRegionPosition === ExtraRegionPosition.BOTTOM) {
750        primaryLayout.size.width = width;
751        secondaryLayout.size.width = width - extraLayout.size.width;
752        extraLayout.size.height = secondaryLayout.size.height;
753        extraLayout.position.x = secondaryLayout.size.width;
754        extraLayout.position.y = secondaryLayout.position.y;
755      } else {
756        extraLayout.size.height = primaryLayout.size.height;
757        primaryLayout.size.width = width - extraLayout.size.width;
758        secondaryLayout.size.width = width;
759        extraLayout.position.x = primaryLayout.position.x + primaryLayout.size.width;
760        extraLayout.position.y = 0;
761      }
762    }
763
764    return { primary: primaryLayout, secondary: secondaryLayout, extra: extraLayout };
765  }
766
767  private getFoldedRegionLayouts(): RegionLayout {
768    const width = this.containerSize.width;
769    const height = this.containerSize.height;
770    const primaryLayout: Layout = initLayout();
771    const secondaryLayout: Layout = initLayout();
772    const extraLayout: Layout = initLayout();
773
774    const verticalSplitRatio =
775      getSplitRatio(this.foldedLayoutOptions.verticalSplitRatio, PresetSplitRatio.LAYOUT_1V1);
776
777    secondaryLayout.size.height = height / (verticalSplitRatio + 1);
778    primaryLayout.size.height = height - secondaryLayout.size.height;
779    extraLayout.size.height = 0;
780    primaryLayout.size.width = width;
781    secondaryLayout.size.width = width;
782    extraLayout.size.width = 0;
783    primaryLayout.position.x = 0;
784    secondaryLayout.position.x = 0;
785    extraLayout.position.x = width;
786    primaryLayout.position.y = 0;
787    secondaryLayout.position.y = primaryLayout.size.height;
788    extraLayout.position.y = 0;
789
790    return { primary: primaryLayout, secondary: secondaryLayout, extra: extraLayout };
791  }
792
793  private getCreaseRegionRect(): display.Rect {
794    const creaseRegion = display.getCurrentFoldCreaseRegion();
795    const rects = creaseRegion.creaseRects;
796    let left: number = 0;
797    let top: number = 0;
798    let width: number = 0;
799    let height: number = 0;
800    if (rects && rects.length) {
801      const rect = rects[0];
802      left = px2vp(rect.left) - this.containerGlobalPosition.x;
803      top = px2vp(rect.top) - this.containerGlobalPosition.y;
804      width = px2vp(rect.width);
805      height = px2vp(rect.height);
806    }
807
808    return { left, top, width, height };
809  }
810
811  private isPortraitOrientation() {
812    const defaultDisplay = display.getDefaultDisplaySync();
813    switch (defaultDisplay.orientation) {
814      case display.Orientation.PORTRAIT:
815      case display.Orientation.PORTRAIT_INVERTED:
816        return true;
817      case display.Orientation.LANDSCAPE:
818      case display.Orientation.LANDSCAPE_INVERTED:
819      default:
820        return false;
821    }
822  }
823}
824