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