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