1/* 2 * Copyright (c) 2023-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 { KeyCode } from '@ohos.multimodalInput.keyCode'; 17import window from '@ohos.window'; 18import common from '@ohos.app.ability.common'; 19import { BusinessError } from '@kit.BasicServicesKit'; 20import hilog from '@ohos.hilog'; 21 22export interface ComposeTitleBarMenuItem { 23 value: ResourceStr; 24 symbolStyle?: SymbolGlyphModifier; 25 isEnabled?: boolean; 26 action?: () => void; 27 label?: ResourceStr; 28 accessibilityText?: ResourceStr; 29 accessibilityLevel?: string; 30 accessibilityDescription?: ResourceStr; 31} 32 33const PUBLIC_MORE: Resource = $r('sys.symbol.dot_grid_2x2'); 34const PUBLIC_BACK: Resource = $r('sys.symbol.arrow_left'); 35const TEXT_EDITABLE_DIALOG = '18.3fp'; 36const IMAGE_SIZE = '64vp'; 37const MAX_DIALOG = '256vp'; 38const MIN_DIALOG = '216vp'; 39const RESOURCE_TYPE_SYMBOL: number = 40000; 40 41class Util { 42 public static isSymbolResource(resourceStr: ResourceStr | undefined | null): boolean { 43 if (!Util.isResourceType(resourceStr)) { 44 return false; 45 } 46 let resource: Resource = resourceStr as Resource; 47 return resource.type === RESOURCE_TYPE_SYMBOL; 48 } 49 50 public static isResourceType(resource: ResourceStr | Resource | undefined | null): boolean { 51 if (!resource) { 52 return false; 53 } 54 if (typeof resource === 'string' || typeof resource === 'undefined') { 55 return false; 56 } 57 return true; 58 } 59} 60 61class ButtonGestureModifier implements GestureModifier { 62 public static readonly longPressTime: number = 500; 63 public static readonly minFontSize: number = 1.75; 64 public fontSize: number = 1; 65 public controller: CustomDialogController | null = null; 66 67 constructor(controller: CustomDialogController | null) { 68 this.controller = controller; 69 } 70 71 applyGesture(event: UIGestureEvent): void { 72 if (this.fontSize >= ButtonGestureModifier.minFontSize) { 73 event.addGesture( 74 new LongPressGestureHandler({ repeat: false, duration: ButtonGestureModifier.longPressTime }) 75 .onAction(() => { 76 if (event) { 77 this.controller?.open(); 78 } 79 }) 80 .onActionEnd(() => { 81 this.controller?.close(); 82 }) 83 ) 84 } else { 85 event.clearGestures(); 86 } 87 } 88} 89 90@Component 91struct ComposeTitleBar { 92 item: ComposeTitleBarMenuItem | undefined = undefined; 93 title: ResourceStr = ''; 94 subtitle: ResourceStr = ''; 95 menuItems?: Array<ComposeTitleBarMenuItem> = []; 96 @State titleMaxWidth: number = 0; 97 @State fontSize: number = 1; 98 private static readonly totalHeight = 56; 99 private static readonly leftPadding = 12; 100 private static readonly rightPadding = 12; 101 private static readonly portraitImageSize = 40; 102 private static readonly portraitImageLeftPadding = 4; 103 private static readonly portraitImageRightPadding = 16; 104 private static instanceCount = 0; 105 @Provide('uniqueId') uniqueId?: number = -1; 106 107 build() { 108 Flex({ 109 justifyContent: FlexAlign.SpaceBetween, 110 alignItems: ItemAlign.Stretch 111 }) { 112 Row() { 113 ImageMenuItem({ 114 item: { 115 value: PUBLIC_BACK, 116 isEnabled: true, 117 action: () => this.getUIContext()?.getRouter()?.back() 118 }, 119 index: -1, 120 itemIndex: -1 121 }) 122 123 Row() { 124 if (this.item !== undefined) { 125 Image(this.item.value) 126 .width(ComposeTitleBar.portraitImageSize) 127 .height(ComposeTitleBar.portraitImageSize) 128 .margin({ 129 left: $r('sys.float.ohos_id_text_paragraph_margin_xs'), 130 right: $r('sys.float.ohos_id_text_paragraph_margin_m') 131 }) 132 .focusable(false) 133 .borderRadius(ImageMenuItem.buttonBorderRadius) 134 } 135 136 Column() { 137 if (this.title !== undefined) { 138 Row() { 139 Text(this.title) 140 .fontWeight(FontWeight.Medium) 141 .fontSize($r('sys.float.ohos_id_text_size_headline8')) 142 .fontColor($r('sys.color.ohos_id_color_titlebar_text')) 143 .maxLines(this.subtitle !== undefined ? 1 : 2) 144 .textOverflow({ overflow: TextOverflow.Ellipsis }) 145 .constraintSize({ maxWidth: this.titleMaxWidth }) 146 } 147 .justifyContent(FlexAlign.Start) 148 } 149 if (this.subtitle !== undefined) { 150 Row() { 151 Text(this.subtitle) 152 .fontSize($r('sys.float.ohos_id_text_size_over_line')) 153 .fontColor($r('sys.color.ohos_id_color_titlebar_subtitle_text')) 154 .maxLines(1) 155 .textOverflow({ overflow: TextOverflow.Ellipsis }) 156 .constraintSize({ maxWidth: this.titleMaxWidth }) 157 } 158 .justifyContent(FlexAlign.Start) 159 } 160 } 161 .justifyContent(FlexAlign.Start) 162 .alignItems(HorizontalAlign.Start) 163 .constraintSize({ maxWidth: this.titleMaxWidth }) 164 } 165 .accessibilityGroup(true) 166 .accessibilityDescription($r('sys.string.subheader_accessibility_title')) 167 } 168 .margin({ left: $r('sys.float.ohos_id_default_padding_start') }) 169 170 if (this.menuItems !== undefined && this.menuItems.length > 0) { 171 CollapsibleMenuSection({ menuItems: this.menuItems, index: 1 + ComposeTitleBar.instanceCount++ }) 172 } 173 } 174 .onAppear(() => { 175 try { 176 this.uniqueId = this.getUIContext().getFrameNodeByUniqueId(this.getUniqueId())?.getFirstChild()?.getUniqueId(); 177 } catch (exception) { 178 let code: number = (exception as BusinessError)?.code; 179 let message: string = (exception as BusinessError)?.message; 180 hilog.error(0x3900, 'ComposeTitleBar', 181 `Faild to getFrameNodeByUniqueId,cause, code: ${code}, message: ${message}`); 182 } 183 }) 184 .width('100%') 185 .height(ComposeTitleBar.totalHeight) 186 .backgroundColor($r('sys.color.ohos_id_color_background')) 187 .onAreaChange((_oldValue: Area, newValue: Area) => { 188 let newWidth = Number(newValue.width); 189 if (this.menuItems !== undefined) { 190 let menusLength = this.menuItems.length; 191 if (menusLength >= CollapsibleMenuSection.maxCountOfVisibleItems) { 192 newWidth = newWidth - ImageMenuItem.imageHotZoneWidth * CollapsibleMenuSection.maxCountOfVisibleItems; 193 } else if (menusLength > 0) { 194 newWidth = newWidth - ImageMenuItem.imageHotZoneWidth * menusLength; 195 } 196 } 197 this.titleMaxWidth = newWidth; 198 this.titleMaxWidth -= ComposeTitleBar.leftPadding; 199 this.titleMaxWidth -= ImageMenuItem.imageHotZoneWidth; 200 if (this.item !== undefined) { 201 this.titleMaxWidth -= ComposeTitleBar.portraitImageLeftPadding + 202 ComposeTitleBar.portraitImageSize + 203 ComposeTitleBar.portraitImageRightPadding; 204 } 205 this.titleMaxWidth -= ComposeTitleBar.rightPadding; 206 }) 207 } 208} 209 210@Component 211struct CollapsibleMenuSection { 212 menuItems?: Array<ComposeTitleBarMenuItem> = []; 213 item: ComposeTitleBarMenuItem = { 214 value: PUBLIC_MORE, 215 label: $r('sys.string.ohos_toolbar_more'), 216 } as ComposeTitleBarMenuItem; 217 index: number = 0; 218 minFontSize: number = 1.75; 219 isFollowingSystemFontScale: boolean = false; 220 maxFontScale: number = 1; 221 systemFontScale?: number = 1; 222 static readonly maxCountOfVisibleItems = 3; 223 private static readonly focusPadding = 4; 224 private static readonly marginsNum = 2; 225 private firstFocusableIndex = -1; 226 @State isPopupShown: boolean = false; 227 @State isMoreIconOnFocus: boolean = false; 228 @State isMoreIconOnHover: boolean = false; 229 @State isMoreIconOnClick: boolean = false; 230 @Prop @Watch('onFontSizeUpdated') fontSize: number = 1; 231 dialogController: CustomDialogController | null = new CustomDialogController({ 232 builder: ComposeTitleBarDialog({ 233 cancel: () => { 234 }, 235 confirm: () => { 236 }, 237 itemComposeTitleDialog: this.item, 238 composeTitleBarDialog: this.item.label ? this.item.label : '', 239 fontSize: this.fontSize, 240 }), 241 maskColor: Color.Transparent, 242 isModal: true, 243 customStyle: true, 244 }) 245 246 @State buttonGestureModifier: ButtonGestureModifier = new ButtonGestureModifier(this.dialogController); 247 248 getMoreIconFgColor() { 249 return this.isMoreIconOnClick ? 250 $r('sys.color.ohos_id_color_titlebar_icon_pressed') : 251 $r('sys.color.ohos_id_color_titlebar_icon'); 252 } 253 254 getMoreIconBgColor() { 255 if (this.isMoreIconOnClick) { 256 return $r('sys.color.ohos_id_color_click_effect'); 257 } else if (this.isMoreIconOnHover) { 258 return $r('sys.color.ohos_id_color_hover'); 259 } else { 260 return Color.Transparent; 261 } 262 } 263 264 aboutToAppear() { 265 try { 266 let uiContent: UIContext = this.getUIContext(); 267 this.isFollowingSystemFontScale = uiContent.isFollowingSystemFontScale(); 268 this.maxFontScale = uiContent.getMaxFontScale(); 269 } catch (err) { 270 let code: number = (err as BusinessError)?.code; 271 let message: string = (err as BusinessError)?.message; 272 hilog.error(0x3900, 'ComposeTitleBar', 273 `Failed to init fontsizescale info, cause, code: ${code}, message: ${message}`); 274 } 275 if (this.menuItems) { 276 this.menuItems.forEach((item, index) => { 277 if (item.isEnabled && this.firstFocusableIndex === -1 && 278 index > CollapsibleMenuSection.maxCountOfVisibleItems - 2) { 279 this.firstFocusableIndex = this.index * 1000 + index + 1; 280 } 281 }) 282 } 283 this.fontSize = this.decideFontScale() 284 } 285 286 decideFontScale(): number { 287 try { 288 let uiContent: UIContext = this.getUIContext(); 289 this.systemFontScale = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1; 290 if (!this.isFollowingSystemFontScale) { 291 return 1; 292 } 293 return Math.min(this.systemFontScale, this.maxFontScale); 294 } catch (exception) { 295 let code: number = (exception as BusinessError)?.code; 296 let message: string = (exception as BusinessError)?.message; 297 hilog.error(0x3900, 'ComposeTitleBar', `Faild to decideFontScale,cause, code: ${code}, message: ${message}`); 298 return 1; 299 } 300 } 301 302 onFontSizeUpdated(): void { 303 this.buttonGestureModifier.fontSize = this.fontSize; 304 } 305 306 build() { 307 Column() { 308 Row() { 309 if (this.menuItems) { 310 if (this.menuItems.length <= CollapsibleMenuSection.maxCountOfVisibleItems) { 311 ForEach(this.menuItems, (item: ComposeTitleBarMenuItem, index: number) => { 312 ImageMenuItem({ item: item, index: this.index * 1000 + index + 1, itemIndex: index }) 313 }) 314 } else { 315 ForEach(this.menuItems.slice(0, CollapsibleMenuSection.maxCountOfVisibleItems - 1), 316 (item: ComposeTitleBarMenuItem, index: number) => { 317 ImageMenuItem({ item: item, index: this.index * 1000 + index + 1, itemIndex: index }) 318 }) 319 320 Button({ type: ButtonType.Normal, stateEffect: true }) { 321 SymbolGlyph(PUBLIC_MORE) 322 .fontSize(`${ImageMenuItem.imageSize}vp`) 323 .fontColor([$r('sys.color.icon_primary')]) 324 .draggable(false) 325 .focusable(true) 326 } 327 .accessibilityText($r('sys.string.ohos_toolbar_more')) 328 .width(ImageMenuItem.imageHotZoneWidth) 329 .height(ImageMenuItem.imageHotZoneWidth) 330 .borderRadius(ImageMenuItem.buttonBorderRadius) 331 .foregroundColor(this.getMoreIconFgColor()) 332 .backgroundColor(this.getMoreIconBgColor()) 333 .stateStyles({ 334 focused: { 335 .border({ 336 radius: $r('sys.float.ohos_id_corner_radius_clicked'), 337 width: ImageMenuItem.focusBorderWidth, 338 color: $r('sys.color.ohos_id_color_focused_outline'), 339 style: BorderStyle.Solid 340 }) 341 }, 342 normal: { 343 .border({ 344 radius: $r('sys.float.ohos_id_corner_radius_clicked'), 345 width: 0 346 }) 347 } 348 }) 349 .onFocus(() => this.isMoreIconOnFocus = true) 350 .onBlur(() => this.isMoreIconOnFocus = false) 351 .onHover((isOn) => this.isMoreIconOnHover = isOn) 352 .onKeyEvent((event) => { 353 if (event.keyCode !== KeyCode.KEYCODE_ENTER && 354 event.keyCode !== KeyCode.KEYCODE_SPACE) { 355 return; 356 } 357 if (event.type === KeyType.Down) { 358 this.isMoreIconOnClick = true; 359 } 360 if (event.type === KeyType.Up) { 361 this.isMoreIconOnClick = false; 362 } 363 }) 364 .onTouch((event) => { 365 if (event.type === TouchType.Down) { 366 this.isMoreIconOnClick = true; 367 } 368 if (event.type === TouchType.Up || event.type === TouchType.Cancel) { 369 this.isMoreIconOnClick = false; 370 if (this.fontSize >= this.minFontSize) { 371 this.dialogController?.close(); 372 } 373 } 374 }) 375 .onClick(() => this.isPopupShown = true) 376 .gestureModifier(this.buttonGestureModifier) 377 .bindPopup(this.isPopupShown, { 378 builder: this.popupBuilder, 379 placement: Placement.Bottom, 380 popupColor: Color.White, 381 enableArrow: false, 382 onStateChange: (e) => { 383 this.isPopupShown = e.isVisible; 384 if (!e.isVisible) { 385 this.isMoreIconOnClick = false; 386 } 387 } 388 }) 389 } 390 } 391 } 392 } 393 .height('100%') 394 .margin({ right: $r('sys.float.ohos_id_default_padding_end') }) 395 .justifyContent(FlexAlign.Center) 396 } 397 398 onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Layoutable[], constraint: ConstraintSizeOptions): void { 399 children.forEach((child) => { 400 child.layout({ x: 0, y: 0 }); 401 }) 402 this.fontSize = this.decideFontScale(); 403 } 404 405 @Builder 406 popupBuilder() { 407 Column() { 408 if (this.menuItems) { 409 ForEach(this.menuItems.slice(CollapsibleMenuSection.maxCountOfVisibleItems - 1, 410 this.menuItems.length), 411 (item: ComposeTitleBarMenuItem, index: number) => { 412 ImageMenuItem({ 413 item: item, index: this.index * 1000 + 414 CollapsibleMenuSection.maxCountOfVisibleItems + index, isPopup: false 415 }) 416 }) 417 } 418 } 419 .width(ImageMenuItem.imageHotZoneWidth + 420 CollapsibleMenuSection.focusPadding * CollapsibleMenuSection.marginsNum) 421 .margin({ top: CollapsibleMenuSection.focusPadding, bottom: CollapsibleMenuSection.focusPadding }) 422 .onAppear(() => { 423 focusControl.requestFocus(ImageMenuItem.focusablePrefix + 424 this.firstFocusableIndex) 425 }) 426 } 427} 428 429@Component 430struct ImageMenuItem { 431 item: ComposeTitleBarMenuItem = {} as ComposeTitleBarMenuItem; 432 index: number = 0; 433 itemIndex?: number = 0; 434 minFontSize: number = 1.75; 435 isFollowingSystemFontScale: boolean = false; 436 maxFontScale: number = 1; 437 systemFontScale?: number = 1; 438 isPopup: boolean = true; 439 static readonly imageSize = 24; 440 static readonly imageHotZoneWidth = 48; 441 static readonly buttonBorderRadius = 8; 442 static readonly focusBorderWidth = 2; 443 static readonly focusablePrefix = 'Id-ComposeTitleBar-ImageMenuItem-'; 444 @State isOnFocus: boolean = false; 445 @State isOnHover: boolean = false; 446 @State isOnClick: boolean = false; 447 @Prop @Watch('onFontSizeUpdated') fontSize: number = 1; 448 @Consume('uniqueId') parentParentUniqueId?: number; 449 dialogController: CustomDialogController | null = new CustomDialogController({ 450 builder: ComposeTitleBarDialog({ 451 cancel: () => { 452 }, 453 confirm: () => { 454 }, 455 itemComposeTitleDialog: this.item, 456 composeTitleBarDialog: this.item.label ? this.item.label : this.textDialog(), 457 fontSize: this.fontSize, 458 }), 459 maskColor: Color.Transparent, 460 isModal: true, 461 customStyle: true, 462 }) 463 464 @State buttonGestureModifier: ButtonGestureModifier = new ButtonGestureModifier(this.dialogController); 465 466 private textDialog(): ResourceStr { 467 if (this.item.value === PUBLIC_MORE) { 468 return $r('sys.string.ohos_toolbar_more'); 469 } else if (this.item.value === PUBLIC_BACK) { 470 return $r('sys.string.icon_back'); 471 } else { 472 return this.item.label ? this.item.label : ''; 473 } 474 } 475 476 private toStringFormat(resource: ResourceStr | undefined): string | undefined { 477 if (typeof resource === 'string') { 478 return resource; 479 } else if (typeof resource === 'undefined') { 480 return ''; 481 } else { 482 let resourceString: string = ''; 483 try { 484 if (resource.id === -1 ) { 485 resourceString = getContext()?.resourceManager?.getStringByNameSync(resource.params?.[0].split('.').pop()); 486 } else { 487 resourceString = getContext()?.resourceManager?.getStringSync(resource); 488 } 489 } catch (err) { 490 let code: number = (err as BusinessError)?.code; 491 let message: string = (err as BusinessError)?.message; 492 hilog.error(0x3900, 'Ace', `Faild to ComposeTitleBar toStringFormat,code: ${code},message:${message}`); 493 } 494 return resourceString; 495 } 496 } 497 498 private getAccessibilityReadText(): string | undefined { 499 if (this.item.value === PUBLIC_BACK) { 500 try { 501 return getContext()?.resourceManager?.getStringByNameSync('icon_back'); 502 } catch (err) { 503 let code: number = (err as BusinessError)?.code; 504 let message: string = (err as BusinessError)?.message; 505 hilog.error(0x3900, 'Ace', 506 `Faild to ComposeTitleBar getStringByNameSync icon_back,code: ${code},message:${message}`); 507 } 508 } else if (this.item.value === PUBLIC_MORE) { 509 try { 510 return getContext()?.resourceManager?.getStringByNameSync('ohos_toolbar_more'); 511 } catch (err) { 512 let code: number = (err as BusinessError)?.code; 513 let message: string = (err as BusinessError)?.message; 514 hilog.error(0x3900, 'Ace', 515 `Faild to ComposeTitleBar getStringByNameSync ohos_toolbar_more,code: ${code},message:${message}`); 516 } 517 } else if (this.item.accessibilityText) { 518 return this.item.accessibilityText as string; 519 } else if (this.item.label) { 520 return this.item.label as string; 521 } 522 return ' '; 523 } 524 525 onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Layoutable[], constraint: ConstraintSizeOptions): void { 526 children.forEach((child) => { 527 child.layout({ x: 0, y: 0 }); 528 }) 529 this.fontSize = this.decideFontScale(); 530 } 531 532 533 getFgColor() { 534 return this.isOnClick 535 ? $r('sys.color.ohos_id_color_titlebar_icon_pressed') 536 : $r('sys.color.ohos_id_color_titlebar_icon'); 537 } 538 539 getBgColor() { 540 if (this.isOnClick) { 541 return $r('sys.color.ohos_id_color_click_effect'); 542 } else if (this.isOnHover) { 543 return $r('sys.color.ohos_id_color_hover'); 544 } else { 545 return Color.Transparent; 546 } 547 } 548 549 aboutToAppear() { 550 try { 551 let uiContent: UIContext = this.getUIContext(); 552 this.isFollowingSystemFontScale = uiContent.isFollowingSystemFontScale(); 553 this.maxFontScale = uiContent.getMaxFontScale(); 554 } catch (err) { 555 let code: number = (err as BusinessError)?.code; 556 let message: string = (err as BusinessError)?.message; 557 hilog.error(0x3900, 'ComposeTitleBar', 558 `Failed to init fontsizescale info, cause, code: ${code}, message: ${message}`); 559 } 560 this.fontSize = this.decideFontScale(); 561 } 562 563 onFontSizeUpdated(): void { 564 this.buttonGestureModifier.fontSize = this.fontSize; 565 } 566 567 decideFontScale(): number { 568 try { 569 let uiContent: UIContext = this.getUIContext(); 570 this.systemFontScale = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1; 571 if (!this.isFollowingSystemFontScale) { 572 return 1; 573 } 574 return Math.min(this.systemFontScale, this.maxFontScale); 575 } catch (exception) { 576 let code: number = (exception as BusinessError)?.code; 577 let message: string = (exception as BusinessError)?.message; 578 hilog.error(0x3900, 'ComposeTitleBar', `Faild to decideFontScale,cause, code: ${code}, message: ${message}`); 579 return 1; 580 } 581 } 582 583 build() { 584 if (this.isPopup) { 585 Button({ type: ButtonType.Normal, stateEffect: this.item.isEnabled }) { 586 if (this.item?.symbolStyle) { 587 SymbolGlyph() 588 .fontColor([$r('sys.color.ohos_id_color_text_primary')]) 589 .attributeModifier(this.item?.symbolStyle) 590 .fontSize(`${ImageMenuItem.imageSize}vp`) 591 .effectStrategy(SymbolEffectStrategy.NONE) 592 .symbolEffect(new SymbolEffect(), false) 593 .draggable(false) 594 .focusable(this.item?.isEnabled) 595 .key(ImageMenuItem.focusablePrefix + this.index) 596 } else { 597 if (Util.isSymbolResource(this.item.value)) { 598 SymbolGlyph(this.item.value as Resource) 599 .fontSize(`${ImageMenuItem.imageSize}vp`) 600 .fontColor([$r('sys.color.ohos_id_color_text_primary')]) 601 .draggable(false) 602 .focusable(this.item?.isEnabled) 603 .key(ImageMenuItem.focusablePrefix + this.index) 604 } else { 605 Image(this.item?.value) 606 .matchTextDirection(this.item?.value === PUBLIC_BACK ? true : false) 607 .width(ImageMenuItem.imageSize) 608 .draggable(false) 609 .height(ImageMenuItem.imageSize) 610 .focusable(this.item?.isEnabled) 611 .key(ImageMenuItem.focusablePrefix + this.index) 612 .fillColor($r('sys.color.ohos_id_color_text_primary')) 613 } 614 } 615 } 616 .id(`ComposeTitleBar_ImageMenuItem_${this.parentParentUniqueId}_${this.itemIndex}`) 617 .accessibilityText(this.getAccessibilityReadText()) 618 .accessibilityLevel(this.item?.accessibilityLevel ?? 'auto') 619 .accessibilityDescription(this.item?.accessibilityDescription as string) 620 .enabled(this.item.isEnabled ? this.item.isEnabled : false) 621 .width(ImageMenuItem.imageHotZoneWidth) 622 .height(ImageMenuItem.imageHotZoneWidth) 623 .borderRadius(ImageMenuItem.buttonBorderRadius) 624 .foregroundColor(this.getFgColor()) 625 .backgroundColor(this.getBgColor()) 626 .stateStyles({ 627 focused: { 628 .border({ 629 radius: $r('sys.float.ohos_id_corner_radius_clicked'), 630 width: ImageMenuItem.focusBorderWidth, 631 color: $r('sys.color.ohos_id_color_focused_outline'), 632 style: BorderStyle.Solid 633 }) 634 }, 635 normal: { 636 .border({ 637 radius: $r('sys.float.ohos_id_corner_radius_clicked'), 638 width: 0 639 }) 640 } 641 }) 642 .onFocus(() => { 643 if (!this.item?.isEnabled) { 644 return; 645 } 646 this.isOnFocus = true; 647 }) 648 .onBlur(() => this.isOnFocus = false) 649 .onHover((isOn) => { 650 if (!this.item?.isEnabled) { 651 return; 652 } 653 this.isOnHover = isOn; 654 }) 655 .onKeyEvent((event) => { 656 if (!this.item?.isEnabled) { 657 return; 658 } 659 if (event.keyCode !== KeyCode.KEYCODE_ENTER && 660 event.keyCode !== KeyCode.KEYCODE_SPACE) { 661 return; 662 } 663 if (event.type === KeyType.Down) { 664 this.isOnClick = true; 665 } 666 if (event.type === KeyType.Up) { 667 this.isOnClick = false; 668 } 669 }) 670 .onTouch((event) => { 671 if (!this.item?.isEnabled) { 672 return; 673 } 674 if (event.type === TouchType.Down) { 675 this.isOnClick = true; 676 } 677 if (event.type === TouchType.Up || event.type === TouchType.Cancel) { 678 this.isOnClick = false; 679 if (this.fontSize >= this.minFontSize) { 680 this.dialogController?.close(); 681 } 682 } 683 }) 684 .onClick(() => { 685 if (this.item) { 686 return this.item.isEnabled && this.item.action?.(); 687 } 688 }) 689 .gestureModifier(this.buttonGestureModifier) 690 } else { 691 Button({ type: ButtonType.Normal, stateEffect: this.item.isEnabled }) { 692 if (this.item?.symbolStyle) { 693 SymbolGlyph() 694 .fontColor([$r('sys.color.ohos_id_color_text_primary')]) 695 .attributeModifier(this.item?.symbolStyle) 696 .fontSize(`${ImageMenuItem.imageSize}vp`) 697 .effectStrategy(SymbolEffectStrategy.NONE) 698 .symbolEffect(new SymbolEffect(), false) 699 .draggable(false) 700 .focusable(this.item?.isEnabled) 701 .key(ImageMenuItem.focusablePrefix + this.index) 702 } else { 703 if (Util.isSymbolResource(this.item.value)) { 704 SymbolGlyph(this.item.value as Resource) 705 .fontSize(`${ImageMenuItem.imageSize}vp`) 706 .fontColor([$r('sys.color.ohos_id_color_text_primary')]) 707 .draggable(false) 708 .focusable(this.item?.isEnabled) 709 .key(ImageMenuItem.focusablePrefix + this.index) 710 } else { 711 Image(this.item?.value) 712 .matchTextDirection(this.item?.value === PUBLIC_BACK ? true : false) 713 .width(ImageMenuItem.imageSize) 714 .draggable(false) 715 .height(ImageMenuItem.imageSize) 716 .focusable(this.item?.isEnabled) 717 .key(ImageMenuItem.focusablePrefix + this.index) 718 .fillColor($r('sys.color.ohos_id_color_text_primary')) 719 } 720 } 721 } 722 .id(`ComposeTitleBar_ImageMenuItem_${this.parentParentUniqueId}_${this.itemIndex}`) 723 .accessibilityText(this.getAccessibilityReadText()) 724 .accessibilityLevel(this.item?.accessibilityLevel ?? 'auto') 725 .accessibilityDescription(this.item?.accessibilityDescription as string) 726 .enabled(this.item.isEnabled ? this.item.isEnabled : false) 727 .width(ImageMenuItem.imageHotZoneWidth) 728 .height(ImageMenuItem.imageHotZoneWidth) 729 .borderRadius(ImageMenuItem.buttonBorderRadius) 730 .foregroundColor(this.getFgColor()) 731 .backgroundColor(this.getBgColor()) 732 .stateStyles({ 733 focused: { 734 .border({ 735 radius: $r('sys.float.ohos_id_corner_radius_clicked'), 736 width: ImageMenuItem.focusBorderWidth, 737 color: $r('sys.color.ohos_id_color_focused_outline'), 738 style: BorderStyle.Solid 739 }) 740 }, 741 normal: { 742 .border({ 743 radius: $r('sys.float.ohos_id_corner_radius_clicked'), 744 width: 0 745 }) 746 } 747 }) 748 .onFocus(() => { 749 if (!this.item?.isEnabled) { 750 return; 751 } 752 this.isOnFocus = true; 753 }) 754 .onBlur(() => this.isOnFocus = false) 755 .onHover((isOn) => { 756 if (!this.item?.isEnabled) { 757 return; 758 } 759 this.isOnHover = isOn; 760 }) 761 .onKeyEvent((event) => { 762 if (!this.item?.isEnabled) { 763 return; 764 } 765 if (event.keyCode !== KeyCode.KEYCODE_ENTER && 766 event.keyCode !== KeyCode.KEYCODE_SPACE) { 767 return; 768 } 769 if (event.type === KeyType.Down) { 770 this.isOnClick = true; 771 } 772 if (event.type === KeyType.Up) { 773 this.isOnClick = false; 774 } 775 }) 776 .onTouch((event) => { 777 if (!this.item?.isEnabled) { 778 return; 779 } 780 if (event.type === TouchType.Down) { 781 this.isOnClick = true; 782 } 783 if (event.type === TouchType.Up || event.type === TouchType.Cancel) { 784 this.isOnClick = false; 785 if (this.fontSize >= this.minFontSize) { 786 this.dialogController?.close(); 787 } 788 } 789 }) 790 .onClick(() => { 791 if (this.item) { 792 return this.item.isEnabled && this.item.action?.(); 793 } 794 }) 795 } 796 797 } 798} 799 800/** 801 * ComposeTitleBarDialog 802 */ 803@CustomDialog 804struct ComposeTitleBarDialog { 805 itemComposeTitleDialog: ComposeTitleBarMenuItem = {} as ComposeTitleBarMenuItem; 806 callbackId: number | undefined = undefined; 807 composeTitleBarDialog?: ResourceStr = ''; 808 mainWindowStage: window.Window | undefined = undefined; 809 controller?: CustomDialogController; 810 minFontSize: number = 1.75; 811 maxFontSize: number = 3.2; 812 screenWidth: number = 640; 813 verticalScreenLines: number = 6; 814 horizontalsScreenLines: number = 1; 815 @StorageLink('mainWindow') mainWindow: Promise<window.Window> | undefined = undefined; 816 @State fontSize: number = 1; 817 @State maxLines: number = 1; 818 @StorageProp('windowStandardHeight') windowStandardHeight: number = 0; 819 cancel: () => void = () => { 820 } 821 confirm: () => void = () => { 822 } 823 824 build() { 825 if (this.composeTitleBarDialog) { 826 Column() { 827 if (this.itemComposeTitleDialog.symbolStyle) { 828 SymbolGlyph() 829 .fontColor([$r('sys.color.icon_primary')]) 830 .attributeModifier(this.itemComposeTitleDialog.symbolStyle) 831 .fontSize(IMAGE_SIZE) 832 .effectStrategy(SymbolEffectStrategy.NONE) 833 .symbolEffect(new SymbolEffect(), false) 834 .margin({ 835 top: $r('sys.float.padding_level24'), 836 bottom: $r('sys.float.padding_level8'), 837 }) 838 } else { 839 if (Util.isSymbolResource(this.itemComposeTitleDialog.value)) { 840 SymbolGlyph(this.itemComposeTitleDialog.value as Resource) 841 .fontSize(IMAGE_SIZE) 842 .fontColor([$r('sys.color.icon_primary')]) 843 .margin({ 844 top: $r('sys.float.padding_level24'), 845 bottom: $r('sys.float.padding_level8'), 846 }) 847 } else { 848 Image(this.itemComposeTitleDialog.value) 849 .width(IMAGE_SIZE) 850 .height(IMAGE_SIZE) 851 .margin({ 852 top: $r('sys.float.padding_level24'), 853 bottom: $r('sys.float.padding_level8'), 854 }) 855 .fillColor($r('sys.color.icon_primary')) 856 } 857 } 858 Column() { 859 Text(this.composeTitleBarDialog) 860 .fontSize(TEXT_EDITABLE_DIALOG) 861 .textOverflow({ overflow: TextOverflow.Ellipsis }) 862 .maxLines(this.maxLines) 863 .width('100%') 864 .textAlign(TextAlign.Center) 865 .fontColor($r('sys.color.font_primary')) 866 } 867 .width('100%') 868 .padding({ 869 left: $r('sys.float.padding_level4'), 870 right: $r('sys.float.padding_level4'), 871 bottom: $r('sys.float.padding_level12'), 872 }) 873 } 874 .width(this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG) 875 .constraintSize({ minHeight: this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG }) 876 .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK, undefined, { disableSystemAdaptation: true }) 877 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 878 .borderRadius($r('sys.float.corner_radius_level10')) 879 } else { 880 Column() { 881 if (this.itemComposeTitleDialog.symbolStyle) { 882 SymbolGlyph() 883 .fontColor([$r('sys.color.icon_primary')]) 884 .attributeModifier(this.itemComposeTitleDialog.symbolStyle) 885 .fontSize(IMAGE_SIZE) 886 .effectStrategy(SymbolEffectStrategy.NONE) 887 .symbolEffect(new SymbolEffect(), false) 888 } else { 889 if (Util.isSymbolResource(this.itemComposeTitleDialog.value)) { 890 SymbolGlyph(this.itemComposeTitleDialog.value as Resource) 891 .fontSize(IMAGE_SIZE) 892 .fontColor([$r('sys.color.icon_primary')]) 893 } else { 894 Image(this.itemComposeTitleDialog.value) 895 .width(IMAGE_SIZE) 896 .height(IMAGE_SIZE) 897 .fillColor($r('sys.color.icon_primary')) 898 } 899 } 900 } 901 .width(this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG) 902 .constraintSize({ minHeight: this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG }) 903 .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK, undefined, { disableSystemAdaptation: true }) 904 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 905 .borderRadius($r('sys.float.corner_radius_level10')) 906 .justifyContent(FlexAlign.Center) 907 } 908 } 909 910 async aboutToAppear(): Promise<void> { 911 try { 912 let context = this.getUIContext().getHostContext() as common.UIAbilityContext; 913 this.mainWindowStage = context.windowStage.getMainWindowSync(); 914 let properties: window.WindowProperties = this.mainWindowStage.getWindowProperties(); 915 let rect = properties.windowRect; 916 if (px2vp(rect.height) > this.screenWidth) { 917 this.maxLines = this.verticalScreenLines; 918 } else { 919 this.maxLines = this.horizontalsScreenLines; 920 } 921 } catch (exception) { 922 let code: number = (exception as BusinessError)?.code; 923 let message: string = (exception as BusinessError)?.message; 924 hilog.error(0x3900, 'ComposeTitleBar', `Faild to getMainWindowSync,cause, code: ${code}, message: ${message}`); 925 } 926 } 927} 928 929export { ComposeTitleBar }