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 try { 186 this.mainWindow.off('windowSizeChange'); 187 } catch (error) { 188 Log.error(TAG, `destroy window error: ${error}`); 189 } 190 } 191 192 isSplitMode(): boolean { 193 return (WindowMode.PRIMARY === this.windowMode || WindowMode.SECONDARY === this.windowMode); 194 } 195 196 async checkWindowMode(): Promise<void> { 197 let before = this.windowMode; 198 let windowStage: window.WindowStage = AppStorage.get<window.WindowStage>('photosWindowStage'); 199 // @ts-ignore 200 let mode: WindowMode = await windowStage.getWindowMode() as WindowMode; 201 Log.info(TAG, `photos application before/current window mode: ${before}/${mode}`); 202 203 if (before == mode) { 204 return; 205 } 206 this.windowMode = mode; 207 if (WindowMode.FULLSCREEN == this.windowMode) { 208 this.setFullScreen(); 209 } else { 210 this.setSplitScreen(); 211 } 212 } 213 214 setMainWindow(): void { 215 Log.debug(TAG, 'setMainWindow'); 216 this.mainWindow.on('windowSizeChange', (data: window.Size) => { 217 Log.debug(TAG, `windowSizeChange ${JSON.stringify(data)}`); 218 try { 219 let properties: window.WindowProperties = this.mainWindow.getWindowProperties(); 220 this.isFullScreen = properties.isFullScreen; 221 } catch (exception) { 222 Log.error(TAG, 'Failed to obtain the area. Cause:' + JSON.stringify(exception)); 223 } 224 this.onWinSizeChanged(data); 225 }) 226 this.mainWindow.getProperties().then((prop: window.WindowProperties) => { 227 Log.info(TAG, `Window prop: ${JSON.stringify(prop)}`); 228 this.onWinSizeChanged(prop.windowRect); 229 }); 230 } 231 232 destroyMainWindow(): void { 233 this.mainWindow.off('windowSizeChange'); 234 } 235 236 getAvoidArea(): void { 237 let topWindow: window.Window = this.getMainWindow(); 238 topWindow.getAvoidArea(0, (err, data: window.AvoidArea) => { 239 Log.info(TAG, 'Succeeded in obtaining the area. Data:' + JSON.stringify(data)); 240 this.onLeftBlankChanged(data); 241 }); 242 } 243 244 setFullScreen(): void { 245 let topWindow: window.Window = this.getMainWindow(); 246 Log.debug(TAG, 'getTopWindow start'); 247 try { 248 topWindow.setLayoutFullScreen(true, () => { 249 Log.debug(TAG, 'setFullScreen true Succeeded'); 250 if (AppStorage.get('deviceType') as string !== Constants.DEFAULT_DEVICE_TYPE) { 251 this.hideStatusBar(); 252 } else { 253 this.setWindowBackgroundColorDefault(true); 254 } 255 }); 256 } catch (err) { 257 Log.error(TAG, `setFullScreen err: ${err}`); 258 } 259 } 260 261 setWindowBackgroundColorDefault(defaultColor: boolean): void { 262 this.getMainWindow().setWindowBackgroundColor(defaultColor ? '#F1F3F5' : '#000000'); 263 } 264 265 setSplitScreen(): void { 266 try { 267 this.statusBarHeight = 0; 268 this.naviBarHeight = 0; 269 this.leftBlank = [0, 0, 0, 0]; 270 this.emit(ScreenManager.ON_LEFT_BLANK_CHANGED, [this.leftBlank]); 271 } catch (err) { 272 Log.error(TAG, `setSplitScreen err: ${err}`); 273 } 274 } 275 276 hideStatusBar(): void { 277 Log.debug(TAG, 'hideStatusBar start'); 278 let topWindow: window.Window = this.getMainWindow(); 279 Log.debug(TAG, 'getTopWindow start'); 280 let names: Array<SystemBarKeys> = new Array<SystemBarKeys>('navigation'); 281 Log.debug(TAG, `getTopWindow names: ${names} end`); 282 try { 283 topWindow.setSystemBarEnable(names, () => { 284 Log.debug(TAG, 'hideStatusBar Succeeded'); 285 topWindow.getAvoidArea(0, async (err, data: window.AvoidArea) => { 286 Log.info(TAG, `Succeeded in obtaining the area. Data: ${JSON.stringify(data)}`); 287 this.onLeftBlankChanged(data); 288 let barColor: string = await UiUtil.getResourceString($r('app.color.transparent')); 289 if (!barColor) { 290 barColor = '#00000000'; 291 } 292 topWindow.setSystemBarProperties({ navigationBarColor: barColor }, () => { 293 Log.info(TAG, 'setStatusBarColor done'); 294 }); 295 }); 296 }); 297 } catch (err) { 298 Log.error(TAG, `hideStatusBar err: ${err}`); 299 } 300 } 301 302 async setDefaultStatusBarProperties(): Promise<void> { 303 Log.debug(TAG, 'setStatusBarColor start'); 304 let topWindow: window.Window = this.getMainWindow(); 305 try { 306 topWindow.setSystemBarProperties( 307 { statusBarColor: Constants.STATUS_BAR_BACKGROUND_COLOR, 308 statusBarContentColor: Constants.STATUS_BAR_CONTENT_COLOR }, () => { 309 Log.info(TAG, 'setStatusBarColor done'); 310 }); 311 } catch (err) { 312 Log.error(TAG, `setStatusBarColor err: ${err}`); 313 } 314 } 315 316 setSystemUi(isShowBar: boolean): void { 317 let deviceTp: string = AppStorage.get('deviceType') as string; 318 Log.debug(TAG, `setSystemUi start, isShowBar=${isShowBar}, deviceType=${deviceTp}`); 319 let topWindow: window.Window = this.getMainWindow(); 320 Log.debug(TAG, 'getTopWindow start'); 321 let names: Array<SystemBarKeys> = new Array<SystemBarKeys>('status', 'navigation'); 322 if (deviceTp === Constants.PC_DEVICE_TYPE || deviceTp === Constants.PAD_DEVICE_TYPE) { 323 names = new Array<SystemBarKeys>('navigation'); 324 } 325 if (!isShowBar) { 326 names = []; 327 } 328 Log.debug(TAG, `getTopWindow names: ${names} end`); 329 try { 330 topWindow.setSystemBarEnable(names, () => { 331 Log.debug(TAG, `setSystemUi Succeeded: ${names}`); 332 if (isShowBar) { 333 topWindow.getAvoidArea(0, (err, data: window.AvoidArea) => { 334 Log.info(TAG, 'Succeeded in obtaining the area. Data:' + JSON.stringify(data)); 335 this.onLeftBlankChanged(data); 336 }); 337 } 338 }) 339 } catch (err) { 340 Log.error(TAG, `setSystemUi err: ${err}`); 341 } 342 } 343 344 isHorizontal(): boolean { 345 if (AppStorage.get(Constants.SCREEN_ORIENTATION_HORIZONTAL) == null) { 346 AppStorage.SetOrCreate(Constants.SCREEN_ORIENTATION_HORIZONTAL, this.horizontal); 347 } 348 return AppStorage.get(Constants.SCREEN_ORIENTATION_HORIZONTAL); 349 } 350 351 isSidebar(): boolean { 352 if (AppStorage.get(Constants.SCREEN_SIDEBAR) == null) { 353 AppStorage.SetOrCreate(Constants.SCREEN_SIDEBAR, this.sidebar); 354 } 355 return AppStorage.get(Constants.SCREEN_SIDEBAR); 356 } 357 358 getColumnsWidth(count: number): number { 359 let columnWidth: number = (this.winWidth - Constants.COLUMN_MARGIN) / this.columns; 360 columnWidth = parseInt((columnWidth * count - Constants.COLUMN_GUTTER) + ''); 361 Log.info(TAG, `getColumnsWidth count is ${count} colunms is ${this.columns}, columnWidth is ${columnWidth} `); 362 return columnWidth; 363 } 364 365 getScreenColumns(): number { 366 return this.columns; 367 } 368 369 onRotationAngleChanged(isH: boolean): void { 370 Log.info(TAG, `onRotationAngleChanged horizontal: ${isH}`); 371 if (isH === null || isH === undefined) { 372 return; 373 } 374 this.horizontal = isH; 375 AppStorage.SetOrCreate(Constants.SCREEN_ORIENTATION_HORIZONTAL, this.horizontal); 376 } 377 378 private getMainWindow(): window.Window { 379 return AppStorage.get<window.Window>('mainWindow'); 380 } 381 382 private emit(event: string, argument: unknown[]): void { 383 this.broadcast.emit(event, argument); 384 } 385 386 private isLeftBlankInitialized(): boolean { 387 return this.leftBlank[0] != 0 || this.leftBlank[1] != 0 || this.leftBlank[2] != 0 || this.leftBlank[3] != 0; 388 } 389 390 private onLeftBlankChanged(area: window.AvoidArea): void { 391 if (area === null || area === undefined || (area.bottomRect.height === 0 && area.topRect.height === 0)) { 392 return; 393 } 394 let leftBlankBefore = { 395 status: this.statusBarHeight, 396 navi: this.naviBarHeight 397 }; 398 // Area data obtained through the system interface, 399 // There is a possibility that the area data is incorrect. 400 AppStorage.SetOrCreate<number>('statusBarHeight', area.topRect.height); 401 this.statusBarHeight = px2vp(area.topRect.height); 402 this.naviBarHeight = px2vp(area.bottomRect.height); 403 this.leftBlank = [this.leftBlank[0], this.leftBlank[1], this.leftBlank[2], px2vp(area.bottomRect.height)]; 404 if (leftBlankBefore.status != this.statusBarHeight || leftBlankBefore.navi != this.naviBarHeight) { 405 Log.info(TAG, `leftBlank changed: ${JSON.stringify(leftBlankBefore)}-${JSON.stringify(this.leftBlank)}`); 406 this.emit(ScreenManager.ON_LEFT_BLANK_CHANGED, [this.leftBlank]); 407 } 408 } 409 410 private onWinSizeChanged(size: window.Size | window.Rect): void { 411 Log.info(TAG, `onWinSizeChanged ${JSON.stringify(size)}`); 412 if (size == null || size == undefined) { 413 return; 414 } 415 let isSplitModeBefore: boolean = this.isSplitMode(); 416 this.checkWindowMode(); 417 let sizeBefore = { 418 width: this.winWidth, 419 height: this.winHeight 420 }; 421 this.winWidth = px2vp(size.width); 422 this.winHeight = px2vp(size.height); 423 Log.info(TAG, `onChanged winSize: ${size.width}*${size.height} px, ${this.winWidth}*${this.winHeight} vp`); 424 if (this.winWidth < ScreenWidth.WIDTH_MEDIUM) { 425 this.columns = ColumnSize.COLUMN_FOUR; 426 } else if (this.winWidth >= ScreenWidth.WIDTH_MEDIUM && this.winWidth < ScreenWidth.WIDTH_LARGE) { 427 this.columns = ColumnSize.COLUMN_EIGHT; 428 } else { 429 this.columns = ColumnSize.COLUMN_TWELVE; 430 } 431 let isSplitModeNow: boolean = this.isSplitMode(); 432 if (isSplitModeBefore != isSplitModeNow) { 433 Log.info(TAG, `splitMode changed: ${isSplitModeBefore} -> ${isSplitModeNow}`); 434 this.emit(ScreenManager.ON_SPLIT_MODE_CHANGED, [isSplitModeNow]); 435 } 436 if (sizeBefore.width != this.winWidth || sizeBefore.height != this.winHeight) { 437 let newSize = { 438 width: this.winWidth, 439 height: this.winHeight 440 }; 441 Log.info(TAG, `winSize changed: ${JSON.stringify(sizeBefore)} -> ${JSON.stringify(newSize)}`); 442 this.emit(ScreenManager.ON_WIN_SIZE_CHANGED, [size]); 443 } 444 } 445} 446