1/* 2 * Copyright (c) 2022-2023 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 { Log } from '../../utils/Log'; 17import { Constants } from './Constants'; 18import { UiUtil } from '../../utils/UiUtil'; 19import { BroadCast } from '../../utils/BroadCast'; 20import type window from '@ohos.window'; 21 22const TAG: string = 'common_ScreenManager'; 23 24type SystemBarKeys = 'status' | 'navigation'; 25 26export enum ColumnSize { 27 COLUMN_ONE_POINT_FIVE = 1.5, 28 COLUMN_TWO = 2, 29 COLUMN_FOUR = 4, 30 COLUMN_SIX = 6, 31 COLUMN_EIGHT = 8, 32 COLUMN_TWELVE = 12 33} 34 35enum ScreenWidth { 36 WIDTH_MEDIUM = 520, 37 WIDTH_LARGE = 840 38} 39 40enum WindowMode { 41 UNDEFINED = 1, 42 FULLSCREEN, 43 PRIMARY, 44 SECONDARY, 45 FLOATING 46} 47 48export class ScreenManager { 49 static readonly ON_WIN_SIZE_CHANGED: string = 'on_win_size_changed'; 50 static readonly ON_SPLIT_MODE_CHANGED: string = 'on_split_mode_changed'; 51 static readonly ON_LEFT_BLANK_CHANGED: string = 'on_left_blank_changed'; 52 static readonly DEFAULT_WIDTH: number = 1920; 53 static readonly DEFAULT_HEIGHT: number = 1080; 54 55 // no interface is available to obtain the height of WindowDecor. WindowDecor px Height: 56. 56 static readonly WIN_DECOR_HEIGHT_PX: number = 56; 57 58 private winWidth: number = 0.0; 59 private winHeight: number = 0.0; 60 private statusBarHeight: number = 0; 61 private naviBarHeight: number = 0; 62 private leftBlank: [number, number, number, number] = [0, 0, 0, 0]; 63 private broadcast: BroadCast = new BroadCast(); 64 private mainWindow: window.Window = undefined; 65 private columns: number = ColumnSize.COLUMN_FOUR; 66 67 // Default orientation for Pc 68 private horizontal: boolean = true; 69 70 // Default sidebar for Pc 71 private sidebar: boolean = true; 72 private windowMode: WindowMode = WindowMode.UNDEFINED; 73 private isFullScreen: boolean = true; 74 75 private constructor() { 76 Log.info(TAG, 'constructor'); 77 this.horizontal = false; 78 this.sidebar = false; 79 } 80 81 static getInstance(): ScreenManager { 82 if (AppStorage.Get(Constants.APP_KEY_SCREEN_MANAGER) == null) { 83 AppStorage.SetOrCreate(Constants.APP_KEY_SCREEN_MANAGER, new ScreenManager()); 84 } 85 let manager: ScreenManager = AppStorage.Get(Constants.APP_KEY_SCREEN_MANAGER); 86 return manager; 87 } 88 89 initializationSize(win: window.Window): Promise<void> { 90 this.mainWindow = win; 91 let properties: window.WindowProperties = win.getWindowProperties(); 92 this.isFullScreen = properties.isFullScreen; 93 94 // Area data obtained through the system interface, 95 // There is a possibility that the area data is incorrect. 96 const statusBarHeight: number = properties && properties.windowRect ? properties.windowRect.top : this.statusBarHeight; 97 AppStorage.SetOrCreate<number>('statusBarHeight', statusBarHeight); 98 return new Promise<void>((resolve, reject) => { 99 if (!properties || !properties.windowRect) { 100 reject(); 101 } 102 let size: window.Rect = properties.windowRect; 103 Log.info(TAG, `display screen windowRect: ${JSON.stringify(size)}`); 104 this.winWidth = px2vp(size.width); 105 this.winHeight = px2vp(size.height); 106 Log.info(TAG, `init winSize: ${size.width}*${size.height} px, ${this.winWidth}*${this.winHeight} vp`); 107 if (this.winWidth < ScreenWidth.WIDTH_MEDIUM) { 108 this.columns = ColumnSize.COLUMN_FOUR; 109 } else if (this.winWidth >= ScreenWidth.WIDTH_MEDIUM && this.winWidth < ScreenWidth.WIDTH_LARGE) { 110 this.columns = ColumnSize.COLUMN_EIGHT; 111 } else { 112 this.columns = ColumnSize.COLUMN_TWELVE; 113 } 114 this.emit(ScreenManager.ON_WIN_SIZE_CHANGED, [size]); 115 resolve(); 116 }); 117 } 118 119 /** 120 * Add Listeners 121 * 122 * @param event 123 * @param fn 124 */ 125 on(event: string, fn: Function): void { 126 this.broadcast.on(event, fn); 127 } 128 129 /** 130 * Delete Listeners 131 * 132 * @param event 133 * @param fn 134 */ 135 off(event: string, fn: Function): void { 136 this.broadcast.off(event, fn); 137 } 138 139 setWinWidth(width: number): void{ 140 this.winWidth = width; 141 } 142 143 // Unit:vp 144 getWinWidth(): number { 145 return this.winWidth; 146 } 147 148 // Unit:vp 149 getWinHeight(): number { 150 return this.winHeight; 151 } 152 153 // Returns the width of the layout area (LayoutWidth = WindowWidth). 154 getWinLayoutWidth(): number { 155 return this.winWidth; 156 } 157 158 // Returns the height of the layout area (LayoutHeight = WindowHeight - WindowDecorHeight). 159 getWinLayoutHeight(): number { 160 let deviceTp: string = AppStorage.Get('deviceType') as string; 161 Log.debug(TAG, `deviceTp=${deviceTp}, isFull=${this.isFullScreen}, winH=${this.winHeight}`); 162 if (deviceTp === Constants.DEFAULT_DEVICE_TYPE) { 163 return this.winHeight; 164 } 165 let winDecorHeight: number = this.isFullScreen ? 0 : px2vp(ScreenManager.WIN_DECOR_HEIGHT_PX); 166 return this.winHeight - winDecorHeight; 167 } 168 169 getStatusBarHeight(): number { 170 return this.statusBarHeight; 171 } 172 173 getNaviBarHeight(): number { 174 return this.naviBarHeight; 175 } 176 177 initWindowMode(): void { 178 Log.debug(TAG, `start to initialize photos application window mode: ${this.windowMode}`); 179 this.checkWindowMode(); 180 this.mainWindow && this.setMainWindow(); 181 } 182 183 destroyWindowMode(): void { 184 Log.debug(TAG, `start to destory photos application window mode: ${this.windowMode}`); 185 this.mainWindow && this.destroyMainWindow(); 186 } 187 188 isSplitMode(): boolean { 189 return (WindowMode.PRIMARY === this.windowMode || WindowMode.SECONDARY === this.windowMode); 190 } 191 192 async checkWindowMode(): Promise<void> { 193 let before = this.windowMode; 194 let mode = await globalThis.photosWindowStage.getWindowMode(); 195 Log.info(TAG, `photos application before/current window mode: ${before}/${mode}`); 196 197 if (before == mode) { 198 return; 199 } 200 this.windowMode = mode; 201 if (WindowMode.FULLSCREEN == this.windowMode) { 202 this.setFullScreen(); 203 } else { 204 this.setSplitScreen(); 205 } 206 } 207 208 setMainWindow(): void { 209 Log.debug(TAG, 'setMainWindow'); 210 this.mainWindow.on('windowSizeChange', (data: window.Size) => { 211 Log.debug(TAG, `windowSizeChange ${JSON.stringify(data)}`); 212 try { 213 let properties: window.WindowProperties = this.mainWindow.getWindowProperties(); 214 this.isFullScreen = properties.isFullScreen; 215 } catch (exception) { 216 Log.error(TAG, 'Failed to obtain the area. Cause:' + JSON.stringify(exception)); 217 } 218 this.onWinSizeChanged(data); 219 }) 220 this.mainWindow.getProperties().then((prop: window.WindowProperties) => { 221 Log.info(TAG, `Window prop: ${JSON.stringify(prop)}`); 222 this.onWinSizeChanged(prop.windowRect); 223 }); 224 } 225 226 destroyMainWindow(): void { 227 this.mainWindow.off('windowSizeChange'); 228 } 229 230 getAvoidArea(): void { 231 let topWindow: window.Window = this.getMainWindow(); 232 topWindow.getAvoidArea(0, (err, data: window.AvoidArea) => { 233 Log.info(TAG, 'Succeeded in obtaining the area. Data:' + JSON.stringify(data)); 234 this.onLeftBlankChanged(data); 235 }); 236 } 237 238 setFullScreen(): void { 239 let topWindow: window.Window = this.getMainWindow(); 240 Log.debug(TAG, 'getTopWindow start'); 241 try { 242 topWindow.setLayoutFullScreen(true, () => { 243 Log.debug(TAG, 'setFullScreen true Succeeded'); 244 if (AppStorage.Get('deviceType') as string !== Constants.DEFAULT_DEVICE_TYPE) { 245 this.hideStatusBar(); 246 } else { 247 this.setWindowBackgroundColorDefault(true); 248 } 249 }); 250 } catch (err) { 251 Log.error(TAG, `setFullScreen err: ${err}`); 252 } 253 } 254 255 setWindowBackgroundColorDefault(defaultColor: boolean): void { 256 this.getMainWindow().setWindowBackgroundColor(defaultColor ? '#F1F3F5' : '#000000'); 257 } 258 259 setSplitScreen(): void { 260 try { 261 this.statusBarHeight = 0; 262 this.naviBarHeight = 0; 263 this.leftBlank = [0, 0, 0, 0]; 264 this.emit(ScreenManager.ON_LEFT_BLANK_CHANGED, [this.leftBlank]); 265 } catch (err) { 266 Log.error(TAG, `setSplitScreen err: ${err}`); 267 } 268 } 269 270 hideStatusBar(): void { 271 Log.debug(TAG, 'hideStatusBar start'); 272 let topWindow: window.Window = this.getMainWindow(); 273 Log.debug(TAG, 'getTopWindow start'); 274 let names: Array<SystemBarKeys> = new Array<SystemBarKeys>('navigation'); 275 Log.debug(TAG, `getTopWindow names: ${names} end`); 276 try { 277 topWindow.setSystemBarEnable(names, () => { 278 Log.debug(TAG, 'hideStatusBar Succeeded'); 279 topWindow.getAvoidArea(0, async (err, data: window.AvoidArea) => { 280 Log.info(TAG, `Succeeded in obtaining the area. Data: ${JSON.stringify(data)}`); 281 this.onLeftBlankChanged(data); 282 let barColor: string = await UiUtil.getResourceString($r('app.color.transparent')); 283 if (!barColor) { 284 barColor = '#00000000'; 285 } 286 topWindow.setSystemBarProperties({ navigationBarColor: barColor }, () => { 287 Log.info(TAG, 'setStatusBarColor done'); 288 }); 289 }); 290 }); 291 } catch (err) { 292 Log.error(TAG, `hideStatusBar err: ${err}`); 293 } 294 } 295 296 async setDefaultStatusBarProperties(): Promise<void> { 297 Log.debug(TAG, 'setStatusBarColor start'); 298 let topWindow: window.Window = this.getMainWindow(); 299 try { 300 topWindow.setSystemBarProperties( 301 { statusBarColor: Constants.STATUS_BAR_BACKGROUND_COLOR, 302 statusBarContentColor: Constants.STATUS_BAR_CONTENT_COLOR }, () => { 303 Log.info(TAG, 'setStatusBarColor done'); 304 }); 305 } catch (err) { 306 Log.error(TAG, `setStatusBarColor err: ${err}`); 307 } 308 } 309 310 setSystemUi(isShowBar: boolean): void { 311 let deviceTp: string = AppStorage.Get('deviceType') as string; 312 Log.debug(TAG, `setSystemUi start, isShowBar=${isShowBar}, deviceType=${deviceTp}`); 313 let topWindow: window.Window = this.getMainWindow(); 314 Log.debug(TAG, 'getTopWindow start'); 315 let names: Array<SystemBarKeys> = new Array<SystemBarKeys>('status', 'navigation'); 316 if (deviceTp === Constants.PC_DEVICE_TYPE || deviceTp === Constants.PAD_DEVICE_TYPE) { 317 names = new Array<SystemBarKeys>('navigation'); 318 } 319 if (!isShowBar) { 320 names = []; 321 } 322 Log.debug(TAG, `getTopWindow names: ${names} end`); 323 try { 324 topWindow.setSystemBarEnable(names, () => { 325 Log.debug(TAG, `setSystemUi Succeeded: ${names}`); 326 if (isShowBar) { 327 topWindow.getAvoidArea(0, (err, data: window.AvoidArea) => { 328 Log.info(TAG, 'Succeeded in obtaining the area. Data:' + JSON.stringify(data)); 329 this.onLeftBlankChanged(data); 330 }); 331 } 332 }) 333 } catch (err) { 334 Log.error(TAG, `setSystemUi err: ${err}`); 335 } 336 } 337 338 isHorizontal(): boolean { 339 if (AppStorage.Get(Constants.SCREEN_ORIENTATION_HORIZONTAL) == null) { 340 AppStorage.SetOrCreate(Constants.SCREEN_ORIENTATION_HORIZONTAL, this.horizontal); 341 } 342 return AppStorage.Get(Constants.SCREEN_ORIENTATION_HORIZONTAL); 343 } 344 345 isSidebar(): boolean { 346 if (AppStorage.Get(Constants.SCREEN_SIDEBAR) == null) { 347 AppStorage.SetOrCreate(Constants.SCREEN_SIDEBAR, this.sidebar); 348 } 349 return AppStorage.Get(Constants.SCREEN_SIDEBAR); 350 } 351 352 getColumnsWidth(count: number): number { 353 let columnWidth: number = (this.winWidth - Constants.COLUMN_MARGIN) / this.columns; 354 columnWidth = parseInt((columnWidth * count - Constants.COLUMN_GUTTER) + ''); 355 Log.info(TAG, `getColumnsWidth count is ${count} colunms is ${this.columns}, columnWidth is ${columnWidth} `); 356 return columnWidth; 357 } 358 359 getScreenColumns(): number { 360 return this.columns; 361 } 362 363 onRotationAngleChanged(isH: boolean): void { 364 Log.info(TAG, `onRotationAngleChanged horizontal: ${isH}`); 365 if (isH === null || isH === undefined) { 366 return; 367 } 368 this.horizontal = isH; 369 AppStorage.SetOrCreate(Constants.SCREEN_ORIENTATION_HORIZONTAL, this.horizontal); 370 } 371 372 private getMainWindow(): window.Window { 373 return AppStorage.Get<window.Window>('mainWindow'); 374 } 375 376 private emit(event: string, argument: unknown[]): void { 377 this.broadcast.emit(event, argument); 378 } 379 380 private isLeftBlankInitialized(): boolean { 381 return this.leftBlank[0] != 0 || this.leftBlank[1] != 0 || this.leftBlank[2] != 0 || this.leftBlank[3] != 0; 382 } 383 384 private onLeftBlankChanged(area: window.AvoidArea): void { 385 if (area === null || area === undefined || (area.bottomRect.height === 0 && area.topRect.height === 0)) { 386 return; 387 } 388 let leftBlankBefore = { 389 status: this.statusBarHeight, 390 navi: this.naviBarHeight 391 }; 392 // Area data obtained through the system interface, 393 // There is a possibility that the area data is incorrect. 394 AppStorage.SetOrCreate<number>('statusBarHeight', area.topRect.height); 395 this.statusBarHeight = px2vp(area.topRect.height); 396 this.naviBarHeight = px2vp(area.bottomRect.height); 397 this.leftBlank = [this.leftBlank[0], this.leftBlank[1], this.leftBlank[2], px2vp(area.bottomRect.height)]; 398 if (leftBlankBefore.status != this.statusBarHeight || leftBlankBefore.navi != this.naviBarHeight) { 399 Log.info(TAG, `leftBlank changed: ${JSON.stringify(leftBlankBefore)}-${JSON.stringify(this.leftBlank)}`); 400 this.emit(ScreenManager.ON_LEFT_BLANK_CHANGED, [this.leftBlank]); 401 } 402 } 403 404 private onWinSizeChanged(size: window.Size | window.Rect): void { 405 Log.info(TAG, `onWinSizeChanged ${JSON.stringify(size)}`); 406 if (size == null || size == undefined) { 407 return; 408 } 409 let isSplitModeBefore: boolean = this.isSplitMode(); 410 this.checkWindowMode(); 411 let sizeBefore = { 412 width: this.winWidth, 413 height: this.winHeight 414 }; 415 this.winWidth = px2vp(size.width); 416 this.winHeight = px2vp(size.height); 417 Log.info(TAG, `onChanged winSize: ${size.width}*${size.height} px, ${this.winWidth}*${this.winHeight} vp`); 418 if (this.winWidth < ScreenWidth.WIDTH_MEDIUM) { 419 this.columns = ColumnSize.COLUMN_FOUR; 420 } else if (this.winWidth >= ScreenWidth.WIDTH_MEDIUM && this.winWidth < ScreenWidth.WIDTH_LARGE) { 421 this.columns = ColumnSize.COLUMN_EIGHT; 422 } else { 423 this.columns = ColumnSize.COLUMN_TWELVE; 424 } 425 let isSplitModeNow: boolean = this.isSplitMode(); 426 if (isSplitModeBefore != isSplitModeNow) { 427 Log.info(TAG, `splitMode changed: ${isSplitModeBefore} -> ${isSplitModeNow}`); 428 this.emit(ScreenManager.ON_SPLIT_MODE_CHANGED, [isSplitModeNow]); 429 } 430 if (sizeBefore.width != this.winWidth || sizeBefore.height != this.winHeight) { 431 let newSize = { 432 width: this.winWidth, 433 height: this.winHeight 434 }; 435 Log.info(TAG, `winSize changed: ${JSON.stringify(sizeBefore)} -> ${JSON.stringify(newSize)}`); 436 this.emit(ScreenManager.ON_WIN_SIZE_CHANGED, [size]); 437 } 438 } 439} 440