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