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}