• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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'
18
19export declare type TabTitleBarMenuItem = {
20  value: ResourceStr
21  isEnabled: boolean
22  action?: () => void
23}
24
25export declare type TabTitleBarTabItem = {
26  title: ResourceStr
27  icon?: ResourceStr
28}
29
30const PUBLIC_MORE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAAIGNIUk0AAHomAACAhAAA+' +
31  'gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAEZ0FNQQAAsY58+1GTAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAO' +
32  'xAAADsQBlSsOGwAABEZJREFUeNrt3D1rFFEUBuAxhmAhFlYpUohYiYWFRcAmKAhWK2pjo1iKf8BCMIKFf8BarCyMhVj4VZhGSKEg2FqJyCKWIhY' +
33  'WnstMINgYsh+cmfs88BICydxw7jmzu2HvNg0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBN+3r6dx+LXIqsRpa7FF8j48hm5Fn3Peo9mAEYRd' +
34  'YjJ3f582Vj7nZfUe/eDsCRyMPI2h5/fyNyI/JDT6v3Tvt7sBllE15ETkxwjeORi5G3ke/6W737MgBnI68jh6ZwrcORq5HnhkC9+zAA5YXXy8jBK' +
35  'V5zKXIu8jjyS7+rd+YBeNVtyrSVO9PRyBM9r94LSTfjWuTUDK9/eYIXeENUbb0zDsBi5PYc1rmj79U74wCszuih+F/ljrSi/+uud8YBGA10rayq' +
36  'rnfGAVgb6FpZVV3vjAOwPNC1sqq63hkHYGWga2VVdb0XKt/8Rf1fd70zDsB4jmt5u3Tl9a59AMb6v+56ZxyArYGulVXV9c44ABtzXOup/q+73hk' +
37  'H4N2cHio/Rj7r/7rrnXEAfkfuz2Gddb2v3ln/DfpgxneLzaY9xE3l9c46AH8iVyI/Z3Dt8nB/Xc+rd5H5QMy3yJemPVs6zY0edc9HUe/0Z4I/dQ' +
38  '/N5Vjd0oTXKp9QcKFpD2qj3r0YgO1NeRM507TH6/bifeR85IMeV++d+vTBWOV9JDcjt5rdv6uw3M3uRR7pa/Xu+wBsOxA53bTnTP/3UX1b3fNQ1' +
39  'BsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKqyr6d/97HIpchqZLlL8TUyjmxGnnXfo96DGYBRZD1ycpc/Xzbm' +
40  'bvcV9e7tAByJPIys7fH3NyI3Ij/0tHrvtL8Hm1E24UXkxATXOB65GHkb+a6/1bsvA3A28jpyaArXOhy5GnluCNS7DwNQXni9jByc4jWXIucijyO' +
41  '/9Lt6Zx6AV92mTFu5Mx2NPNHz6r2QdDOuRU7N8PqXJ3iBN0TV1jvjACxGbs9hnTv6Xr0zDsDqjB6K/1XuSCv6v+56ZxyA0UDXyqrqemccgLWBrp' +
42  'VV1fXOOADLA10rq6rrnXEAVga6VlZV13uh8s1f1P911zvjAIznuJa3S1de79oHYKz/6653xgHYGuhaWVVd74wDsDHHtZ7q/7rrnXEA3s3pofJj5' +
43  'LP+r7veGQfgd+T+HNZZ1/vqnfXfoA9mfLfYbNpD3FRe76wD8CdyJfJzBtcuD/fX9bx6F5kPxHyLfGnas6XT3OhR93wU9U5/JvhT99BcjtUtTXit' +
44  '8gkFF5r2oDbq3YsB2N6UN5EzTXu8bi/eR85HPuhx9d6pTx+MVd5HcjNyq9n9uwrL3exe5JG+Vu++D8C2A5HTTXvO9H8f1bfVPQ9FvQEAAAAAAAA' +
45  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAgCn7C9HjBtwWfXpKAAAAAElFTkSuQmCC'
46
47@Component
48export struct TabTitleBar {
49  tabItems: Array<TabTitleBarTabItem>
50  menuItems: Array<TabTitleBarMenuItem>
51  @BuilderParam swiperContent: () => void
52
53  @State tabWidth: number = 0
54  @State currentIndex: number = 0
55
56  static readonly totalHeight = 56
57  static readonly correctionOffset = -40.0
58  static readonly gradientMaskWidth = 24
59  private static instanceCount = 0
60
61  private menuSectionWidth = 0
62  private tabOffsets = Array<number>()
63  private imageWidths = Array<number>()
64
65  private scroller: Scroller = new Scroller()
66  private swiperController: SwiperController = new SwiperController()
67  private settings: RenderingContextSettings = new RenderingContextSettings(true)
68  private leftContext2D: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
69  private rightContext2D: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
70
71  @Builder
72  GradientMask(context2D: CanvasRenderingContext2D, x0: number, y0: number, x1: number, y1: number) {
73    Column() {
74      Canvas(context2D)
75        .width(TabTitleBar.gradientMaskWidth)
76        .height(TabTitleBar.totalHeight)
77        .onReady(() => {
78          var grad = context2D.createLinearGradient(x0, y0, x1, y1)
79          grad.addColorStop(0.0, '#ffffffff')
80          grad.addColorStop(1, '#00ffffff')
81          context2D.fillStyle = grad
82          context2D.fillRect(0, 0, TabTitleBar.gradientMaskWidth, TabTitleBar.totalHeight)
83        })
84    }
85    .width(TabTitleBar.gradientMaskWidth)
86    .height(TabTitleBar.totalHeight)
87  }
88
89  aboutToAppear() {
90    this.tabItems.forEach((_elem) => {
91      this.imageWidths.push(0)
92    })
93    this.loadOffsets()
94  }
95
96  loadOffsets() {
97    this.tabOffsets.length = 0
98
99    let tabOffset = 0
100    this.tabOffsets.push(tabOffset)
101    tabOffset += TabContentItem.marginFirst
102
103    this.tabItems.forEach((tabItem, index) => {
104      if (tabItem.icon !== undefined) {
105        if (Math.abs(this.imageWidths[index]) > TabContentItem.imageHotZoneWidth) {
106          tabOffset += this.imageWidths[index]
107        } else {
108          tabOffset += TabContentItem.imageHotZoneWidth
109        }
110      } else {
111        tabOffset += TabContentItem.paddingLeft
112        tabOffset += px2vp(MeasureText.measureText({
113          textContent: tabItem.title.toString(),
114          fontSize: 18,
115          fontWeight: FontWeight.Medium,
116        }))
117        tabOffset += TabContentItem.paddingRight
118      }
119      this.tabOffsets.push(tabOffset)
120    })
121  }
122
123  build() {
124    Column() {
125      Flex({
126        justifyContent: FlexAlign.SpaceBetween,
127        alignItems: ItemAlign.Stretch
128      }) {
129        Stack({ alignContent: Alignment.End }) {
130          Stack({ alignContent: Alignment.Start }) {
131            Column() {
132              List({ initialIndex: 0, scroller: this.scroller, space: 0 }) {
133                ForEach(this.tabItems, (tabItem, index) => {
134                  ListItem() {
135                    TabContentItem({
136                      item: tabItem,
137                      index: index,
138                      maxIndex: this.tabItems.length - 1,
139                      currentIndex: this.currentIndex,
140                      onCustomClick: (itemIndex) => this.currentIndex = itemIndex,
141                      onImageComplete: (width) => {
142                        this.imageWidths[index] = width
143                        this.loadOffsets()
144                      }
145                    })
146                  }
147                })
148              }
149              .width('100%')
150              .height(TabTitleBar.totalHeight)
151              .constraintSize({ maxWidth: this.tabWidth })
152              .edgeEffect(EdgeEffect.Spring)
153              .listDirection(Axis.Horizontal)
154              .scrollBar(BarState.Off)
155            }
156            this.GradientMask(this.leftContext2D, 0, TabTitleBar.totalHeight / 2,
157              TabTitleBar.gradientMaskWidth, TabTitleBar.totalHeight / 2)
158          }
159          this.GradientMask(this.rightContext2D, TabTitleBar.gradientMaskWidth,
160            TabTitleBar.totalHeight / 2, 0, TabTitleBar.totalHeight / 2)
161        }
162
163        if (this.menuItems !== undefined && this.menuItems.length > 0) {
164          CollapsibleMenuSection({ menuItems: this.menuItems, index: 1 + TabTitleBar.instanceCount++ })
165            .height(TabTitleBar.totalHeight)
166            .onAreaChange((_oldValue, newValue) => {
167              this.menuSectionWidth = Number(newValue.width)
168            })
169        }
170      }
171      .backgroundColor($r('sys.color.ohos_id_color_background'))
172      .margin({ right: $r('sys.float.ohos_id_max_padding_end') })
173      .onAreaChange((_oldValue, newValue) => {
174        this.tabWidth = Number(newValue.width) - this.menuSectionWidth
175      })
176
177      Column() {
178        Swiper(this.swiperController) { this.swiperContent() }
179        .index(this.currentIndex)
180        .itemSpace(0)
181        .indicator(false)
182        .width('100%')
183        .height('100%')
184        .curve(Curve.Friction)
185        .onChange((index) => {
186          const offset = this.tabOffsets[index] + TabTitleBar.correctionOffset
187          this.currentIndex = index
188          this.scroller.scrollTo({
189            xOffset: offset > 0 ? offset : 0,
190            yOffset: 0,
191            animation: {
192              duration: 300,
193              curve: Curve.EaseInOut
194            }
195          })
196        })
197        .onAppear(() => {
198          this.scroller.scrollToIndex(this.currentIndex)
199          this.scroller.scrollBy(TabTitleBar.correctionOffset, 0)
200        })
201      }
202    }
203  }
204}
205
206@Component
207struct CollapsibleMenuSection {
208  menuItems: Array<TabTitleBarMenuItem>
209  index: number
210
211  static readonly maxCountOfVisibleItems = 1
212  private static readonly focusPadding = 4
213  private static readonly marginsNum = 2
214  private firstFocusableIndex = -1
215
216  @State isPopupShown: boolean = false
217
218  @State isMoreIconOnFocus: boolean = false
219  @State isMoreIconOnHover: boolean = false
220  @State isMoreIconOnClick: boolean = false
221
222  getMoreIconFgColor() {
223    return this.isMoreIconOnClick
224      ? $r('sys.color.ohos_id_color_titlebar_icon_pressed')
225      : $r('sys.color.ohos_id_color_titlebar_icon')
226  }
227
228  getMoreIconBgColor() {
229    if (this.isMoreIconOnClick) {
230      return $r('sys.color.ohos_id_color_click_effect')
231    } else if (this.isMoreIconOnHover) {
232      return $r('sys.color.ohos_id_color_hover')
233    } else {
234      return Color.Transparent
235    }
236  }
237
238  aboutToAppear() {
239    this.menuItems.forEach((item, index) => {
240      if (item.isEnabled && this.firstFocusableIndex == -1 && index > CollapsibleMenuSection.maxCountOfVisibleItems - 2) {
241        this.firstFocusableIndex = this.index * 1000 + index + 1
242      }
243    })
244  }
245
246  build() {
247    Column() {
248      Row() {
249        if (this.menuItems.length <= CollapsibleMenuSection.maxCountOfVisibleItems) {
250          ForEach(this.menuItems, (item, index) => {
251            ImageMenuItem({ item: item, index: this.index * 1000 + index + 1 })
252          })
253        } else {
254          ForEach(this.menuItems.slice(0, CollapsibleMenuSection.maxCountOfVisibleItems - 1), (item, index) => {
255            ImageMenuItem({ item: item, index: this.index * 1000 + index + 1 })
256          })
257
258          Row() {
259            Image(PUBLIC_MORE)
260              .width(ImageMenuItem.imageSize)
261              .height(ImageMenuItem.imageSize)
262              .focusable(true)
263          }
264          .width(ImageMenuItem.imageHotZoneWidth)
265          .height(ImageMenuItem.imageHotZoneWidth)
266          .borderRadius(ImageMenuItem.buttonBorderRadius)
267          .foregroundColor(this.getMoreIconFgColor())
268          .backgroundColor(this.getMoreIconBgColor())
269          .justifyContent(FlexAlign.Center)
270          .stateStyles({
271            focused: {
272              .border({
273                radius: $r('sys.float.ohos_id_corner_radius_clicked'),
274                width: ImageMenuItem.focusBorderWidth,
275                color: $r('sys.color.ohos_id_color_focused_outline'),
276                style: BorderStyle.Solid
277              })
278            },
279            normal: {
280              .border({
281                radius: $r('sys.float.ohos_id_corner_radius_clicked'),
282                width: 0
283              })
284            }
285          })
286          .onFocus(() => this.isMoreIconOnFocus = true)
287          .onBlur(() => this.isMoreIconOnFocus = false)
288          .onHover((isOn) => this.isMoreIconOnHover = isOn)
289          .onKeyEvent((event) => {
290            if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) {
291              return
292            }
293            if (event.type === KeyType.Down) {
294              this.isMoreIconOnClick = true
295            }
296            if (event.type === KeyType.Up) {
297              this.isMoreIconOnClick = false
298            }
299          })
300          .onTouch((event) => {
301            if (event.type === TouchType.Down) {
302              this.isMoreIconOnClick = true
303            }
304            if (event.type === TouchType.Up) {
305              this.isMoreIconOnClick = false
306            }
307          })
308          .onClick(() => this.isPopupShown = true)
309          .bindPopup(this.isPopupShown, {
310            builder: this.popupBuilder,
311            placement: Placement.Bottom,
312            popupColor: Color.White,
313            enableArrow: false,
314            onStateChange: (e) => {
315              this.isPopupShown = e.isVisible
316              if (!e.isVisible) {
317                this.isMoreIconOnClick = false
318              }
319            }
320          })
321        }
322      }
323    }
324    .height('100%')
325    .justifyContent(FlexAlign.Center)
326  }
327
328  @Builder
329  popupBuilder() {
330    Column() {
331      ForEach(this.menuItems.slice(CollapsibleMenuSection.maxCountOfVisibleItems - 1, this.menuItems.length), (item, index) => {
332        ImageMenuItem({ item: item, index: this.index * 1000 + CollapsibleMenuSection.maxCountOfVisibleItems + index })
333      })
334    }
335    .width(ImageMenuItem.imageHotZoneWidth + CollapsibleMenuSection.focusPadding * CollapsibleMenuSection.marginsNum)
336    .margin({ top: CollapsibleMenuSection.focusPadding, bottom: CollapsibleMenuSection.focusPadding })
337    .onAppear(() => {
338      focusControl.requestFocus(ImageMenuItem.focusablePrefix + this.firstFocusableIndex)
339    })
340  }
341}
342
343@Component
344struct TabContentItem {
345  item: TabTitleBarTabItem
346  index: number
347  maxIndex: number
348  onCustomClick?: (index: number) => void
349  onImageComplete?: (width: number) => void
350
351  @Prop currentIndex: number
352
353  @State isOnFocus: boolean = false
354  @State isOnHover: boolean = false
355  @State isOnClick: boolean = false
356  @State tabWidth: number = 0
357
358  @State imageWidth: number = 24
359  @State imageHeight: number = 24
360
361  static readonly imageSize = 24
362  static readonly imageHotZoneWidth = 48
363  static readonly imageMagnificationFactor = 1.4
364  static readonly buttonBorderRadius = 8
365  static readonly focusBorderWidth = 2
366  static readonly paddingLeft = 8
367  static readonly paddingRight = 8
368  static readonly marginFirst = 16
369
370  getBgColor() {
371    if (this.isOnClick) {
372      return $r('sys.color.ohos_id_color_click_effect')
373    } else if (this.isOnHover) {
374      return $r('sys.color.ohos_id_color_hover')
375    } else {
376      return Color.Transparent
377    }
378  }
379
380  getBorderAttr() {
381    if (this.isOnFocus) {
382      return {
383        radius: $r('sys.float.ohos_id_corner_radius_clicked'),
384        width: TabContentItem.focusBorderWidth,
385        color: $r('sys.color.ohos_id_color_focused_outline'),
386        style: BorderStyle.Solid
387      }
388    }
389    return { width: 0 }
390  }
391
392  getImageScaleFactor(): number {
393    return this.index === this.currentIndex ? TabContentItem.imageMagnificationFactor : 1
394  }
395
396  getImageLayoutWidth(): number {
397    return TabContentItem.imageSize / Math.max(this.imageHeight, 1.0) * this.imageWidth
398  }
399
400  build() {
401    Stack() {
402      Row() {
403        Column() {
404          if (this.item.icon === undefined) {
405            Text(this.item.title)
406              .fontSize(this.index === this.currentIndex
407                ? $r('sys.float.ohos_id_text_size_headline7')
408                : $r('sys.float.ohos_id_text_size_headline9'))
409              .fontColor(this.index === this.currentIndex
410                ? $r('sys.color.ohos_id_color_titlebar_text')
411                : $r('sys.color.ohos_id_color_titlebar_text_off'))
412              .fontWeight(FontWeight.Medium)
413              .focusable(true)
414              .animation({ duration: 300 })
415              .padding({
416                top: this.index === this.currentIndex ? 6 : 10,
417                left: TabContentItem.paddingLeft,
418                bottom: 2,
419                right: TabContentItem.paddingRight
420              })
421              .onFocus(() => this.isOnFocus = true)
422              .onBlur(() => this.isOnFocus = false)
423              .onHover((isOn) => this.isOnHover = isOn)
424              .onKeyEvent((event) => {
425                if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) {
426                  return
427                }
428                if (event.type === KeyType.Down) {
429                  this.isOnClick = true
430                }
431                if (event.type === KeyType.Up) {
432                  this.isOnClick = false
433                }
434              })
435              .onTouch((event) => {
436                if (event.type === TouchType.Down) {
437                  this.isOnClick = true
438                }
439                if (event.type === TouchType.Up) {
440                  this.isOnClick = false
441                }
442              })
443              .onClick(() => this.onCustomClick && this.onCustomClick(this.index))
444          } else {
445            Row() {
446              Image(this.item.icon)
447                .alt(this.item.title)
448                .width(this.getImageLayoutWidth())
449                .height(TabContentItem.imageSize)
450                .objectFit(ImageFit.Fill)
451                .scale({
452                  x: this.getImageScaleFactor(),
453                  y: this.getImageScaleFactor()
454                })
455                .animation({ duration: 300 })
456                .hitTestBehavior(HitTestMode.None)
457                .focusable(true)
458                .onComplete((event) => {
459                  if (!this.onImageComplete) {
460                    return
461                  }
462                  this.imageWidth = px2vp(event.width)
463                  this.imageHeight = px2vp(event.height)
464                  this.onImageComplete(px2vp(event.componentWidth)
465                    + TabContentItem.paddingLeft + TabContentItem.paddingRight)
466                })
467                .onError((event) => {
468                  if (!this.onImageComplete) {
469                    return
470                  }
471                  this.onImageComplete(px2vp(event.componentWidth)
472                    + TabContentItem.paddingLeft + TabContentItem.paddingRight)
473                })
474            }
475            .width(this.getImageLayoutWidth() * this.getImageScaleFactor()
476              + TabContentItem.paddingLeft + TabContentItem.paddingRight)
477            .constraintSize({
478              minWidth: TabContentItem.imageHotZoneWidth,
479              minHeight: TabContentItem.imageHotZoneWidth
480            })
481            .animation({ duration: 300 })
482            .justifyContent(FlexAlign.Center)
483            .onFocus(() => this.isOnFocus = true)
484            .onBlur(() => this.isOnFocus = false)
485            .onHover((isOn) => this.isOnHover = isOn)
486            .onKeyEvent((event) => {
487              if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) {
488                return
489              }
490              if (event.type === KeyType.Down) {
491                this.isOnClick = true
492              }
493              if (event.type === KeyType.Up) {
494                this.isOnClick = false
495              }
496            })
497            .onTouch((event) => {
498              if (event.type === TouchType.Down) {
499                this.isOnClick = true
500              }
501              if (event.type === TouchType.Up) {
502                this.isOnClick = false
503              }
504            })
505            .onClick(() => this.onCustomClick && this.onCustomClick(this.index))
506          }
507        }
508        .justifyContent(FlexAlign.Center)
509      }
510      .height(TabTitleBar.totalHeight)
511      .alignItems(VerticalAlign.Center)
512      .justifyContent(FlexAlign.Center)
513      .borderRadius(TabContentItem.buttonBorderRadius)
514      .backgroundColor(this.getBgColor())
515      .onAreaChange((_oldValue, newValue) => {
516        this.tabWidth = Number(newValue.width)
517      })
518
519      if (this.isOnFocus && this.tabWidth > 0) {
520        Row()
521          .width(this.tabWidth)
522          .height(TabTitleBar.totalHeight)
523          .hitTestBehavior(HitTestMode.None)
524          .borderRadius(TabContentItem.buttonBorderRadius)
525          .stateStyles({
526            focused: {
527              .border(this.getBorderAttr())
528            },
529            normal: {
530              .border({
531                radius: $r('sys.float.ohos_id_corner_radius_clicked'),
532                width: 0
533              })
534            }
535          })
536      }
537    }
538    .margin({
539      left: this.index === 0 ? TabContentItem.marginFirst : 0,
540      right: this.index === this.maxIndex ? 12 : 0
541    })
542  }
543}
544
545@Component
546struct ImageMenuItem {
547  item: TabTitleBarMenuItem
548  index: number
549
550  static readonly imageSize = 24
551  static readonly imageHotZoneWidth = 48
552  static readonly buttonBorderRadius = 8
553  static readonly focusBorderWidth = 2
554  static readonly disabledImageOpacity = 0.4
555  static readonly focusablePrefix = "Id-TabTitleBar-ImageMenuItem-"
556
557  @State isOnFocus: boolean = false
558  @State isOnHover: boolean = false
559  @State isOnClick: boolean = false
560
561  getFgColor() {
562    return this.isOnClick
563      ? $r('sys.color.ohos_id_color_titlebar_icon_pressed')
564      : $r('sys.color.ohos_id_color_titlebar_icon')
565  }
566
567  getBgColor() {
568    if (this.isOnClick) {
569      return $r('sys.color.ohos_id_color_click_effect')
570    } else if (this.isOnHover) {
571      return $r('sys.color.ohos_id_color_hover')
572    } else {
573      return Color.Transparent
574    }
575  }
576
577  build() {
578    Row() {
579      Image(this.item.value)
580        .width(ImageMenuItem.imageSize)
581        .height(ImageMenuItem.imageSize)
582        .focusable(this.item.isEnabled)
583        .key(ImageMenuItem.focusablePrefix + this.index)
584    }
585    .width(ImageMenuItem.imageHotZoneWidth)
586    .height(ImageMenuItem.imageHotZoneWidth)
587    .borderRadius(ImageMenuItem.buttonBorderRadius)
588    .foregroundColor(this.getFgColor())
589    .backgroundColor(this.getBgColor())
590    .justifyContent(FlexAlign.Center)
591    .opacity(this.item.isEnabled ? 1 : ImageMenuItem.disabledImageOpacity)
592    .stateStyles({
593      focused: {
594        .border({
595          radius: $r('sys.float.ohos_id_corner_radius_clicked'),
596          width: ImageMenuItem.focusBorderWidth,
597          color: $r('sys.color.ohos_id_color_focused_outline'),
598          style: BorderStyle.Solid
599        })
600      },
601      normal: {
602        .border({
603          radius: $r('sys.float.ohos_id_corner_radius_clicked'),
604          width: 0
605        })
606      }
607    })
608    .onFocus(() => {
609      if (!this.item.isEnabled) {
610        return
611      }
612      this.isOnFocus = true
613    })
614    .onBlur(() => this.isOnFocus = false)
615    .onHover((isOn) => {
616      if (!this.item.isEnabled) {
617        return
618      }
619      this.isOnHover = isOn
620    })
621    .onKeyEvent((event) => {
622      if (!this.item.isEnabled) {
623        return
624      }
625      if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) {
626        return
627      }
628      if (event.type === KeyType.Down) {
629        this.isOnClick = true
630      }
631      if (event.type === KeyType.Up) {
632        this.isOnClick = false
633      }
634    })
635    .onTouch((event) => {
636      if (!this.item.isEnabled) {
637        return
638      }
639      if (event.type === TouchType.Down) {
640        this.isOnClick = true
641      }
642      if (event.type === TouchType.Up) {
643        this.isOnClick = false
644      }
645    })
646    .onClick(() => this.item.isEnabled && this.item.action && this.item.action())
647  }
648}
649
650export default { TabTitleBar }