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