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