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