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 MeasureText from '@ohos.measure' 18import window from '@ohos.window' 19import common from '@ohos.app.ability.common' 20import { BusinessError } from '@kit.BasicServicesKit' 21import { hilog } from '@kit.PerformanceAnalysisKit' 22 23export interface TabTitleBarMenuItem { 24 value: ResourceStr; 25 isEnabled?: boolean; 26 action?: () => void; 27 label?: ResourceStr; 28} 29 30export interface TabTitleBarTabItem { 31 title: ResourceStr; 32 icon?: ResourceStr; 33} 34 35const PUBLIC_MORE = $r('sys.media.ohos_ic_public_more') 36const TEXT_EDITABLE_DIALOG = '18.3fp' 37const IMAGE_SIZE = '64vp' 38const MAX_DIALOG = '256vp' 39const MIN_DIALOG = '216vp' 40 41@Component 42export struct TabTitleBar { 43 tabItems: Array<TabTitleBarTabItem> = []; 44 menuItems: Array<TabTitleBarMenuItem> = []; 45 @BuilderParam swiperContent: () => void 46 47 @State tabWidth: number = 0 48 @State currentIndex: number = 0 49 @State fontSize: number = 1 50 51 static readonly totalHeight = 56 52 static readonly correctionOffset = -40.0 53 static readonly gradientMaskWidth = 24 54 private static instanceCount = 0 55 56 private menuSectionWidth = 0 57 private tabOffsets: Array<number> = []; 58 private imageWidths: Array<number> = []; 59 60 private scroller: Scroller = new Scroller() 61 private swiperController: SwiperController = new SwiperController() 62 private settings: RenderingContextSettings = new RenderingContextSettings(true) 63 private leftContext2D: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) 64 private rightContext2D: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) 65 66 @Builder 67 GradientMask(context2D: CanvasRenderingContext2D, x0: number, y0: number, x1: number, y1: number) { 68 Column() { 69 Canvas(context2D) 70 .width(TabTitleBar.gradientMaskWidth) 71 .height(TabTitleBar.totalHeight) 72 .onReady(() => { 73 let grad = context2D.createLinearGradient(x0, y0, x1, y1); 74 grad.addColorStop(0.0, '#ffffffff') 75 grad.addColorStop(1, '#00ffffff') 76 context2D.fillStyle = grad 77 context2D.fillRect(0, 0, TabTitleBar.gradientMaskWidth, TabTitleBar.totalHeight) 78 }) 79 } 80 .blendMode(BlendMode.DST_OUT) 81 .width(TabTitleBar.gradientMaskWidth) 82 .height(TabTitleBar.totalHeight) 83 } 84 85 aboutToAppear() { 86 this.tabItems.forEach((_elem) => { 87 this.imageWidths.push(0) 88 }) 89 this.loadOffsets() 90 } 91 92 loadOffsets() { 93 this.tabOffsets.length = 0 94 95 let tabOffset = 0 96 this.tabOffsets.push(tabOffset) 97 tabOffset += TabContentItem.marginFirst 98 99 this.tabItems.forEach((tabItem, index) => { 100 if (tabItem.icon !== undefined) { 101 if (Math.abs(this.imageWidths[index]) > TabContentItem.imageHotZoneWidth) { 102 tabOffset += this.imageWidths[index] 103 } else { 104 tabOffset += TabContentItem.imageHotZoneWidth 105 } 106 } else { 107 tabOffset += TabContentItem.paddingLeft 108 tabOffset += px2vp(MeasureText.measureText({ 109 textContent: tabItem.title.toString(), 110 fontSize: 18, 111 fontWeight: FontWeight.Medium, 112 })) 113 tabOffset += TabContentItem.paddingRight 114 } 115 this.tabOffsets.push(tabOffset) 116 }) 117 } 118 119 build() { 120 Column() { 121 Flex({ 122 justifyContent: FlexAlign.SpaceBetween, 123 alignItems: ItemAlign.Stretch 124 }) { 125 Stack({ alignContent: Alignment.End }) { 126 Stack({ alignContent: Alignment.Start }) { 127 Column() { 128 List({ initialIndex: 0, scroller: this.scroller, space: 0 }) { 129 ForEach(this.tabItems, (tabItem: TabTitleBarTabItem, index: number) => { 130 ListItem() { 131 TabContentItem({ 132 item: tabItem, 133 index: index, 134 maxIndex: this.tabItems.length - 1, 135 currentIndex: this.currentIndex, 136 onCustomClick: (itemIndex) => this.currentIndex = itemIndex, 137 onImageComplete: (width) => { 138 this.imageWidths[index] = width 139 this.loadOffsets() 140 } 141 }) 142 } 143 }) 144 } 145 .width('100%') 146 .height(TabTitleBar.totalHeight) 147 .constraintSize({ maxWidth: this.tabWidth }) 148 .edgeEffect(EdgeEffect.Spring) 149 .listDirection(Axis.Horizontal) 150 .scrollBar(BarState.Off) 151 } 152 this.GradientMask(this.leftContext2D, 0, TabTitleBar.totalHeight / 2, 153 TabTitleBar.gradientMaskWidth, TabTitleBar.totalHeight / 2) 154 } 155 this.GradientMask(this.rightContext2D, TabTitleBar.gradientMaskWidth, 156 TabTitleBar.totalHeight / 2, 0, TabTitleBar.totalHeight / 2) 157 } 158 .blendMode(BlendMode.SRC_OVER, BlendApplyType.OFFSCREEN) 159 160 if (this.menuItems !== undefined && this.menuItems.length > 0) { 161 CollapsibleMenuSection({ menuItems: this.menuItems, index: 1 + TabTitleBar.instanceCount++ }) 162 .height(TabTitleBar.totalHeight) 163 .onAreaChange((_oldValue, newValue) => { 164 this.menuSectionWidth = Number(newValue.width) 165 }) 166 } 167 } 168 .backgroundColor($r('sys.color.ohos_id_color_background')) 169 .margin({ right: $r('sys.float.ohos_id_max_padding_end') }) 170 .onAreaChange((_oldValue, newValue) => { 171 this.tabWidth = Number(newValue.width) - this.menuSectionWidth 172 }) 173 174 Column() { 175 Swiper(this.swiperController) { this.swiperContent() } 176 .index(this.currentIndex) 177 .itemSpace(0) 178 .indicator(false) 179 .width('100%') 180 .height('100%') 181 .curve(Curve.Friction) 182 .onChange((index) => { 183 const offset = this.tabOffsets[index] + TabTitleBar.correctionOffset 184 this.currentIndex = index 185 this.scroller.scrollTo({ 186 xOffset: offset > 0 ? offset : 0, 187 yOffset: 0, 188 animation: { 189 duration: 300, 190 curve: Curve.EaseInOut 191 } 192 }) 193 }) 194 .onAppear(() => { 195 this.scroller.scrollToIndex(this.currentIndex) 196 this.scroller.scrollBy(TabTitleBar.correctionOffset, 0) 197 }) 198 } 199 } 200 } 201} 202 203@Component 204struct CollapsibleMenuSection { 205 menuItems: Array<TabTitleBarMenuItem> = []; 206 index: number = 0; 207 item: TabTitleBarMenuItem = { 208 value: PUBLIC_MORE, 209 label: $r('sys.string.ohos_toolbar_more'), 210 } as TabTitleBarMenuItem; 211 longPressTime: number = 500; 212 minFontSize: number = 1.75; 213 isFollowingSystemFontScale: boolean = false; 214 maxFontScale: number = 1; 215 systemFontScale?: number = 1; 216 217 static readonly maxCountOfVisibleItems = 1 218 private static readonly focusPadding = 4 219 private static readonly marginsNum = 2 220 private firstFocusableIndex = -1 221 222 @State isPopupShown: boolean = false 223 224 @State isMoreIconOnFocus: boolean = false 225 @State isMoreIconOnHover: boolean = false 226 @State isMoreIconOnClick: boolean = false 227 @Prop fontSize: number = 1 228 229 dialogController: CustomDialogController | null = new CustomDialogController({ 230 builder: TabTitleBarDialog({ 231 cancel: () => { 232 }, 233 confirm: () => { 234 }, 235 tabTitleDialog: this.item, 236 tabTitleBarDialog: this.item.label ? this.item.label : '', 237 fontSize: this.fontSize, 238 }), 239 maskColor: Color.Transparent, 240 isModal: true, 241 customStyle: true, 242 }) 243 244 getMoreIconFgColor() { 245 return this.isMoreIconOnClick 246 ? $r('sys.color.ohos_id_color_titlebar_icon_pressed') 247 : $r('sys.color.ohos_id_color_titlebar_icon') 248 } 249 250 getMoreIconBgColor() { 251 if (this.isMoreIconOnClick) { 252 return $r('sys.color.ohos_id_color_click_effect') 253 } else if (this.isMoreIconOnHover) { 254 return $r('sys.color.ohos_id_color_hover') 255 } else { 256 return Color.Transparent 257 } 258 } 259 260 aboutToAppear() { 261 try { 262 let uiContent: UIContext = this.getUIContext(); 263 this.isFollowingSystemFontScale = uiContent.isFollowingSystemFontScale(); 264 this.maxFontScale = uiContent.getMaxFontScale(); 265 } catch (exception) { 266 let code: number = (exception as BusinessError).code; 267 let message: string = (exception as BusinessError).message; 268 hilog.error(0x3900, 'Ace', `Faild to decideFontScale,cause, code: ${code}, message: ${message}`); 269 } 270 this.menuItems.forEach((item, index) => { 271 if (item.isEnabled && this.firstFocusableIndex == -1 && 272 index > CollapsibleMenuSection.maxCountOfVisibleItems - 2) { 273 this.firstFocusableIndex = this.index * 1000 + index + 1 274 } 275 }) 276 } 277 278 decideFontScale(): number { 279 let uiContent: UIContext = this.getUIContext(); 280 this.systemFontScale = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1; 281 if (!this.isFollowingSystemFontScale) { 282 return 1; 283 } 284 return Math.min(this.systemFontScale, this.maxFontScale); 285 } 286 287 build() { 288 Column() { 289 Row() { 290 if (this.menuItems.length <= CollapsibleMenuSection.maxCountOfVisibleItems) { 291 ForEach(this.menuItems, (item: TabTitleBarMenuItem, index: number) => { 292 ImageMenuItem({ item: item, index: this.index * 1000 + index + 1 }) 293 }) 294 } else { 295 ForEach(this.menuItems.slice(0, CollapsibleMenuSection.maxCountOfVisibleItems - 1), 296 (item: TabTitleBarMenuItem, index: number) => { 297 ImageMenuItem({ item: item, index: this.index * 1000 + index + 1 }) 298 }) 299 300 Row() { 301 Image(PUBLIC_MORE) 302 .width(ImageMenuItem.imageSize) 303 .height(ImageMenuItem.imageSize) 304 .focusable(true) 305 .draggable(false) 306 .fillColor($r('sys.color.icon_primary')) 307 } 308 .width(ImageMenuItem.imageHotZoneWidth) 309 .height(ImageMenuItem.imageHotZoneWidth) 310 .borderRadius(ImageMenuItem.buttonBorderRadius) 311 .foregroundColor(this.getMoreIconFgColor()) 312 .backgroundColor(this.getMoreIconBgColor()) 313 .justifyContent(FlexAlign.Center) 314 .stateStyles({ 315 focused: { 316 .border({ 317 radius: $r('sys.float.ohos_id_corner_radius_clicked'), 318 width: ImageMenuItem.focusBorderWidth, 319 color: $r('sys.color.ohos_id_color_focused_outline'), 320 style: BorderStyle.Solid 321 }) 322 }, 323 normal: { 324 .border({ 325 radius: $r('sys.float.ohos_id_corner_radius_clicked'), 326 width: 0 327 }) 328 } 329 }) 330 .onFocus(() => this.isMoreIconOnFocus = true) 331 .onBlur(() => this.isMoreIconOnFocus = false) 332 .onHover((isOn) => this.isMoreIconOnHover = isOn) 333 .onKeyEvent((event) => { 334 if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) { 335 return 336 } 337 if (event.type === KeyType.Down) { 338 this.isMoreIconOnClick = true 339 } 340 if (event.type === KeyType.Up) { 341 this.isMoreIconOnClick = false 342 } 343 }) 344 .onTouch((event) => { 345 if (event.type === TouchType.Down) { 346 this.isMoreIconOnClick = true 347 } 348 if (event.type === TouchType.Up || event.type === TouchType.Cancel) { 349 this.isMoreIconOnClick = false 350 if (this.fontSize >= this.minFontSize) { 351 this.dialogController?.close() 352 } 353 } 354 }) 355 .onClick(() => this.isPopupShown = true) 356 .gesture( 357 LongPressGesture({ repeat: false, duration: this.longPressTime }) 358 .onAction((event: GestureEvent) => { 359 this.fontSize = this.decideFontScale(); 360 if (event) { 361 if (this.fontSize >= this.minFontSize) { 362 this.dialogController?.open() 363 } 364 } 365 })) 366 .bindPopup(this.isPopupShown, { 367 builder: this.popupBuilder, 368 placement: Placement.Bottom, 369 popupColor: Color.White, 370 enableArrow: false, 371 onStateChange: (e) => { 372 this.isPopupShown = e.isVisible 373 if (!e.isVisible) { 374 this.isMoreIconOnClick = false 375 } 376 } 377 }) 378 } 379 } 380 } 381 .height('100%') 382 .justifyContent(FlexAlign.Center) 383 } 384 385 @Builder 386 popupBuilder() { 387 Column() { 388 ForEach(this.menuItems.slice(CollapsibleMenuSection.maxCountOfVisibleItems - 1, this.menuItems.length), 389 (item: TabTitleBarMenuItem, index: number) => { 390 ImageMenuItem({ item: item, index: this.index * 1000 + 391 CollapsibleMenuSection.maxCountOfVisibleItems + index }) 392 }) 393 } 394 .width(ImageMenuItem.imageHotZoneWidth + CollapsibleMenuSection.focusPadding * CollapsibleMenuSection.marginsNum) 395 .margin({ top: CollapsibleMenuSection.focusPadding, bottom: CollapsibleMenuSection.focusPadding }) 396 .onAppear(() => { 397 focusControl.requestFocus(ImageMenuItem.focusablePrefix + this.firstFocusableIndex) 398 }) 399 } 400} 401 402@Component 403struct TabContentItem { 404 item: TabTitleBarTabItem = { title: '' }; 405 index: number = 0; 406 maxIndex: number = 0; 407 onCustomClick?: (index: number) => void 408 onImageComplete?: (width: number) => void 409 410 @Prop currentIndex: number 411 412 @State isOnFocus: boolean = false 413 @State isOnHover: boolean = false 414 @State isOnClick: boolean = false 415 @State tabWidth: number = 0 416 417 @State imageWidth: number = 24 418 @State imageHeight: number = 24 419 420 static readonly imageSize = 24 421 static readonly imageHotZoneWidth = 48 422 static readonly imageMagnificationFactor = 1.4 423 static readonly buttonBorderRadius = 8 424 static readonly focusBorderWidth = 2 425 static readonly paddingLeft = 8 426 static readonly paddingRight = 8 427 static readonly marginFirst = 16 428 429 getBgColor() { 430 if (this.isOnClick) { 431 return $r('sys.color.ohos_id_color_click_effect') 432 } else if (this.isOnHover) { 433 return $r('sys.color.ohos_id_color_hover') 434 } else { 435 return Color.Transparent 436 } 437 } 438 439 getBorderAttr(): BorderOptions { 440 if (this.isOnFocus) { 441 return { 442 radius: $r('sys.float.ohos_id_corner_radius_clicked'), 443 width: TabContentItem.focusBorderWidth, 444 color: $r('sys.color.ohos_id_color_focused_outline'), 445 style: BorderStyle.Solid 446 } 447 } 448 return { width: 0 } 449 } 450 451 getImageScaleFactor(): number { 452 return this.index === this.currentIndex ? TabContentItem.imageMagnificationFactor : 1 453 } 454 455 getImageLayoutWidth(): number { 456 return TabContentItem.imageSize / Math.max(this.imageHeight, 1.0) * this.imageWidth 457 } 458 459 build() { 460 Stack() { 461 Row() { 462 Column() { 463 if (this.item.icon === undefined) { 464 Text(this.item.title) 465 .fontSize(this.index === this.currentIndex 466 ? $r('sys.float.ohos_id_text_size_headline7') 467 : $r('sys.float.ohos_id_text_size_headline9')) 468 .fontColor(this.index === this.currentIndex 469 ? $r('sys.color.ohos_id_color_titlebar_text') 470 : $r('sys.color.ohos_id_color_titlebar_text_off')) 471 .fontWeight(FontWeight.Medium) 472 .focusable(true) 473 .animation({ duration: 300 }) 474 .padding({ 475 top: this.index === this.currentIndex ? 6 : 10, 476 left: TabContentItem.paddingLeft, 477 bottom: 2, 478 right: TabContentItem.paddingRight 479 }) 480 .onFocus(() => this.isOnFocus = true) 481 .onBlur(() => this.isOnFocus = false) 482 .onHover((isOn) => this.isOnHover = isOn) 483 .onKeyEvent((event) => { 484 if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) { 485 return 486 } 487 if (event.type === KeyType.Down) { 488 this.isOnClick = true 489 } 490 if (event.type === KeyType.Up) { 491 this.isOnClick = false 492 } 493 }) 494 .onTouch((event) => { 495 if (event.type === TouchType.Down) { 496 this.isOnClick = true 497 } 498 if (event.type === TouchType.Up) { 499 this.isOnClick = false 500 } 501 }) 502 .onClick(() => this.onCustomClick && this.onCustomClick(this.index)) 503 } else { 504 Row() { 505 Image(this.item.icon) 506 .alt(this.item.title) 507 .width(this.getImageLayoutWidth()) 508 .height(TabContentItem.imageSize) 509 .objectFit(ImageFit.Fill) 510 .scale({ 511 x: this.getImageScaleFactor(), 512 y: this.getImageScaleFactor() 513 }) 514 .animation({ duration: 300 }) 515 .hitTestBehavior(HitTestMode.None) 516 .focusable(true) 517 .onComplete((event) => { 518 if (!this.onImageComplete) { 519 return 520 } 521 this.imageWidth = px2vp(event?.width); 522 this.imageHeight = px2vp(event?.height); 523 this.onImageComplete(px2vp(event?.componentWidth) + 524 TabContentItem.paddingLeft + TabContentItem.paddingRight); 525 }) 526 .onError((event) => { 527 if (!this.onImageComplete) { 528 return 529 } 530 this.onImageComplete(px2vp(event.componentWidth) + 531 TabContentItem.paddingLeft + TabContentItem.paddingRight) 532 }) 533 } 534 .width(this.getImageLayoutWidth() * this.getImageScaleFactor() + 535 TabContentItem.paddingLeft + TabContentItem.paddingRight) 536 .constraintSize({ 537 minWidth: TabContentItem.imageHotZoneWidth, 538 minHeight: TabContentItem.imageHotZoneWidth 539 }) 540 .animation({ duration: 300 }) 541 .justifyContent(FlexAlign.Center) 542 .onFocus(() => this.isOnFocus = true) 543 .onBlur(() => this.isOnFocus = false) 544 .onHover((isOn) => this.isOnHover = isOn) 545 .onKeyEvent((event) => { 546 if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) { 547 return 548 } 549 if (event.type === KeyType.Down) { 550 this.isOnClick = true 551 } 552 if (event.type === KeyType.Up) { 553 this.isOnClick = false 554 } 555 }) 556 .onTouch((event) => { 557 if (event.type === TouchType.Down) { 558 this.isOnClick = true 559 } 560 if (event.type === TouchType.Up) { 561 this.isOnClick = false 562 } 563 }) 564 .onClick(() => this.onCustomClick && this.onCustomClick(this.index)) 565 } 566 } 567 .justifyContent(FlexAlign.Center) 568 } 569 .height(TabTitleBar.totalHeight) 570 .alignItems(VerticalAlign.Center) 571 .justifyContent(FlexAlign.Center) 572 .borderRadius(TabContentItem.buttonBorderRadius) 573 .backgroundColor(this.getBgColor()) 574 .onAreaChange((_oldValue, newValue) => { 575 this.tabWidth = Number(newValue.width) 576 }) 577 578 if (this.isOnFocus && this.tabWidth > 0) { 579 Row() 580 .width(this.tabWidth) 581 .height(TabTitleBar.totalHeight) 582 .hitTestBehavior(HitTestMode.None) 583 .borderRadius(TabContentItem.buttonBorderRadius) 584 .stateStyles({ 585 focused: { 586 .border(this.getBorderAttr()) 587 }, 588 normal: { 589 .border({ 590 radius: $r('sys.float.ohos_id_corner_radius_clicked'), 591 width: 0 592 }) 593 } 594 }) 595 } 596 } 597 .margin({ 598 left: this.index === 0 ? TabContentItem.marginFirst : 0, 599 right: this.index === this.maxIndex ? 12 : 0 600 }) 601 } 602} 603 604@Component 605struct ImageMenuItem { 606 item: TabTitleBarMenuItem = { value: '' }; 607 index: number = 0; 608 609 static readonly imageSize = 24 610 static readonly imageHotZoneWidth = 48 611 static readonly buttonBorderRadius = 8 612 static readonly focusBorderWidth = 2 613 static readonly disabledImageOpacity = 0.4 614 static readonly focusablePrefix = "Id-TabTitleBar-ImageMenuItem-" 615 616 @State isOnFocus: boolean = false 617 @State isOnHover: boolean = false 618 @State isOnClick: boolean = false 619 620 getFgColor() { 621 return this.isOnClick 622 ? $r('sys.color.ohos_id_color_titlebar_icon_pressed') 623 : $r('sys.color.ohos_id_color_titlebar_icon') 624 } 625 626 getBgColor() { 627 if (this.isOnClick) { 628 return $r('sys.color.ohos_id_color_click_effect') 629 } else if (this.isOnHover) { 630 return $r('sys.color.ohos_id_color_hover') 631 } else { 632 return Color.Transparent 633 } 634 } 635 636 build() { 637 Row() { 638 Image(this.item.value) 639 .width(ImageMenuItem.imageSize) 640 .height(ImageMenuItem.imageSize) 641 .focusable(this.item.isEnabled) 642 .key(ImageMenuItem.focusablePrefix + this.index) 643 } 644 .width(ImageMenuItem.imageHotZoneWidth) 645 .height(ImageMenuItem.imageHotZoneWidth) 646 .borderRadius(ImageMenuItem.buttonBorderRadius) 647 .foregroundColor(this.getFgColor()) 648 .backgroundColor(this.getBgColor()) 649 .justifyContent(FlexAlign.Center) 650 .opacity(this.item.isEnabled ? 1 : ImageMenuItem.disabledImageOpacity) 651 .stateStyles({ 652 focused: { 653 .border({ 654 radius: $r('sys.float.ohos_id_corner_radius_clicked'), 655 width: ImageMenuItem.focusBorderWidth, 656 color: $r('sys.color.ohos_id_color_focused_outline'), 657 style: BorderStyle.Solid 658 }) 659 }, 660 normal: { 661 .border({ 662 radius: $r('sys.float.ohos_id_corner_radius_clicked'), 663 width: 0 664 }) 665 } 666 }) 667 .onFocus(() => { 668 if (!this.item.isEnabled) { 669 return 670 } 671 this.isOnFocus = true 672 }) 673 .onBlur(() => this.isOnFocus = false) 674 .onHover((isOn) => { 675 if (!this.item.isEnabled) { 676 return 677 } 678 this.isOnHover = isOn 679 }) 680 .onKeyEvent((event) => { 681 if (!this.item.isEnabled) { 682 return 683 } 684 if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) { 685 return 686 } 687 if (event.type === KeyType.Down) { 688 this.isOnClick = true 689 } 690 if (event.type === KeyType.Up) { 691 this.isOnClick = false 692 } 693 }) 694 .onTouch((event) => { 695 if (!this.item.isEnabled) { 696 return 697 } 698 if (event.type === TouchType.Down) { 699 this.isOnClick = true 700 } 701 if (event.type === TouchType.Up) { 702 this.isOnClick = false 703 } 704 }) 705 .onClick(() => this.item.isEnabled && this.item.action && this.item.action()) 706 } 707} 708 709/** 710 * TabTitleBarDialog 711 */ 712@CustomDialog 713struct TabTitleBarDialog { 714 tabTitleDialog: TabTitleBarMenuItem = { value: '' }; 715 callbackId: number | undefined = undefined; 716 tabTitleBarDialog?: ResourceStr = ''; 717 mainWindowStage: window.Window | undefined = undefined; 718 controller?: CustomDialogController 719 minFontSize: number = 1.75; 720 maxFontSize: number = 3.2; 721 screenWidth: number = 640; 722 verticalScreenLines: number = 6; 723 horizontalsScreenLines: number = 1; 724 @StorageLink('mainWindow') mainWindow: Promise<window.Window> | undefined = undefined; 725 @State fontSize: number = 1; 726 @State maxLines: number = 1; 727 @StorageProp('windowStandardHeight') windowStandardHeight: number = 0; 728 cancel: () => void = () => { 729 } 730 confirm: () => void = () => { 731 } 732 733 build() { 734 if (this.tabTitleBarDialog) { 735 Column() { 736 Image(this.tabTitleDialog.value) 737 .width(IMAGE_SIZE) 738 .height(IMAGE_SIZE) 739 .margin({ 740 top: $r('sys.float.padding_level24'), 741 bottom: $r('sys.float.padding_level8'), 742 }) 743 .fillColor($r('sys.color.icon_primary')) 744 Column() { 745 Text(this.tabTitleBarDialog) 746 .fontSize(TEXT_EDITABLE_DIALOG) 747 .textOverflow({ overflow: TextOverflow.Ellipsis }) 748 .maxLines(this.maxLines) 749 .width('100%') 750 .textAlign(TextAlign.Center) 751 .fontColor($r('sys.color.font_primary')) 752 } 753 .width('100%') 754 .padding({ 755 left: $r('sys.float.padding_level4'), 756 right: $r('sys.float.padding_level4'), 757 bottom: $r('sys.float.padding_level12'), 758 }) 759 } 760 .width(this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG) 761 .constraintSize({ minHeight: this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG }) 762 .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK) 763 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 764 .borderRadius($r('sys.float.corner_radius_level10')) 765 } else { 766 Column() { 767 Image(this.tabTitleDialog.value) 768 .width(IMAGE_SIZE) 769 .height(IMAGE_SIZE) 770 .fillColor($r('sys.color.icon_primary')) 771 } 772 .width(this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG) 773 .constraintSize({ minHeight: this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG }) 774 .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK) 775 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 776 .borderRadius($r('sys.float.corner_radius_level10')) 777 .justifyContent(FlexAlign.Center) 778 } 779 } 780 781 async aboutToAppear(): Promise<void> { 782 let context = this.getUIContext().getHostContext() as common.UIAbilityContext; 783 this.mainWindowStage = context.windowStage.getMainWindowSync(); 784 let properties: window.WindowProperties = this.mainWindowStage.getWindowProperties(); 785 let rect = properties.windowRect; 786 if (px2vp(rect.height) > this.screenWidth) { 787 this.maxLines = this.verticalScreenLines; 788 } else { 789 this.maxLines = this.horizontalsScreenLines; 790 } 791 } 792}