• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}