• 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 { BusinessError } from '@ohos.base';
17import hilog from '@ohos.hilog';
18import { KeyCode } from '@ohos.multimodalInput.keyCode';
19import resourceManager from '@ohos.resourceManager';
20import { Theme } from '@ohos.arkui.theme';
21import { LengthMetrics } from '@ohos.arkui.node';
22import common from '@ohos.app.ability.common';
23import window from '@ohos.window';
24import { Context } from '@ohos.arkui.UIContext';
25import { SymbolGlyphModifier } from '@kit.ArkUI';
26import { EnvironmentCallback } from '@kit.AbilityKit';
27
28export enum EditableLeftIconType {
29  Back,
30  Cancel,
31}
32
33export declare interface EditableTitleBarMenuItem {
34  value: ResourceStr;
35  symbolStyle?: SymbolGlyphModifier;
36  isEnabled: boolean;
37  label?: ResourceStr;
38  action?: () => void;
39  accessibilityLevel?: string;
40  accessibilityText?: ResourceStr;
41  accessibilityDescription?: ResourceStr;
42  defaultFocus?: boolean;
43}
44
45export type EditableTitleBarItem = EditableTitleBarMenuItem;
46
47export declare interface EditableTitleBarOptions {
48  backgroundColor?: ResourceColor;
49  backgroundBlurStyle?: BlurStyle;
50  safeAreaTypes?: Array<SafeAreaType>;
51  safeAreaEdges?: Array<SafeAreaEdge>;
52}
53
54enum ItemType {
55  Image,
56  Icon,
57  LeftIcon,
58}
59
60const PUBLIC_CANCEL = $r('sys.symbol.xmark');
61
62const PUBLIC_OK = $r('sys.symbol.checkmark');
63
64const PUBLIC_BACK = $r('sys.symbol.chevron_backward');
65
66const PUBLIC_IMAGE_BACK = $r('sys.media.ohos_ic_compnent_titlebar_back');
67
68const DEFAULT_TITLE_BAR_HEIGHT = 56;
69const DEFAULT_TITLE_PADDING = 2;
70const MAX_LINE_ONE = 1;
71const MAX_LINES_TWO = 2;
72const MAX_MAIN_TITLE_PERCENT = 0.65;
73const MAX_SUB_TITLE_PERCENT = 0.35;
74const MIN_SUBTITLE_SIZE = '10.0vp';
75const TEXT_EDITABLE_DIALOG = '18.3fp';
76const IMAGE_SIZE = '64vp';
77const MAX_DIALOG = '256vp';
78const MIN_DIALOG = '216vp';
79const SYMBOL_SIZE = '24vp';
80const SYMBOL_TITLE_SIZE = '64vp';
81const TITLE_VP: number = 20;
82const SUBTITLE_VP: number = 14;
83
84// 'sys.float.titlebar_title_tertiary_size' id,value: 20vp
85const TITLE_F: number = getNumberByResource(125831095, TITLE_VP);
86// 'sys.float.titlebar_subheader_size' id,value: 14vp
87const SUBTITLE_F: number = getNumberByResource(125831097, SUBTITLE_VP);
88
89const TITLE_F_VP: string = (TITLE_F > 0 ? TITLE_F : TITLE_VP) + 'vp';
90const SUBTITLE_F_VP: string = (SUBTITLE_F > 0 ? SUBTITLE_F : SUBTITLE_VP) + 'vp';
91
92class EditableTitleBarTheme {
93  public iconColor: ResourceColor = $r('sys.color.titlebar_icon_color');
94  public iconBackgroundColor: ResourceColor = $r('sys.color.titlebar_icon_background_color');
95  public iconBackgroundPressedColor: ResourceColor = $r('sys.color.titlebar_icon_background_pressed_color');
96  public iconBackgroundHoverColor: ResourceColor = $r('sys.color.titlebar_icon_background_hover_color');
97  public iconBackgroundFocusOutlineColor: ResourceColor = $r('sys.color.titlebar_icon_background_focus_outline_color');
98  public titleColor: ResourceColor = $r('sys.color.titlebar_title_tertiary_color');
99  public subTitleColor: ResourceColor = $r('sys.color.titlebar_subheader_color');
100}
101
102class ButtonGestureModifier implements GestureModifier {
103  public static readonly longPressTime: number = 500;
104  public static readonly minFontSize: number = 1.75;
105  public fontSize: number = 1;
106  public controller: CustomDialogController | null = null;
107
108  constructor(controller: CustomDialogController | null) {
109    this.controller = controller;
110  }
111
112  applyGesture(event: UIGestureEvent): void {
113    if (this.fontSize >= ButtonGestureModifier.minFontSize) {
114      event.addGesture(
115        new LongPressGestureHandler({ repeat: false, duration: ButtonGestureModifier.longPressTime })
116          .onAction(() => {
117            if (event) {
118              this.controller?.open();
119            }
120          })
121          .onActionEnd(() => {
122            this.controller?.close();
123          })
124      )
125    } else {
126      event.clearGestures();
127    }
128  }
129}
130
131@Component
132export struct EditableTitleBar {
133  leftIconStyle: EditableLeftIconType = EditableLeftIconType.Back;
134  title: ResourceStr = '';
135  subtitle?: ResourceStr = '';
136  isSaveIconRequired: boolean = true;
137  imageItem?: EditableTitleBarItem;
138  menuItems: Array<EditableTitleBarMenuItem> | undefined = undefined;
139  options: EditableTitleBarOptions = {};
140  onSave?: () => void;
141  onCancel?: () => void;
142  constraintWidth: number = 0;
143  leftIconDefaultFocus?: boolean = false;
144  saveIconDefaultFocus?: boolean = false;
145  public static readonly maxCountOfExtraItems = 3;
146  public static readonly maxOtherCountOfExtraItems = 2;
147  public static readonly commonZero = 0;
148  public static readonly noneColor = '#00000000';
149  // 'sys.float.titlebar_default_height' id,value: 56vp
150  public static readonly defaultHeight: number = getNumberByResource(125831115, DEFAULT_TITLE_BAR_HEIGHT);
151  // 'sys.float.padding_level1' id,value: 2vp
152  public static readonly defaultTitlePadding: number = getNumberByResource(125830920, DEFAULT_TITLE_PADDING);
153  public static readonly totalHeight: number =
154    EditableTitleBar.defaultHeight === EditableTitleBar.commonZero ? DEFAULT_TITLE_BAR_HEIGHT :
155    EditableTitleBar.defaultHeight;
156  static readonly titlePadding: number =
157    EditableTitleBar.defaultTitlePadding === EditableTitleBar.commonZero ?
158      DEFAULT_TITLE_PADDING : EditableTitleBar.defaultTitlePadding;
159  private static readonly maxMainTitleHeight =
160    (EditableTitleBar.totalHeight - EditableTitleBar.titlePadding) * MAX_MAIN_TITLE_PERCENT;
161  private static readonly maxSubTitleHeight =
162    (EditableTitleBar.totalHeight - EditableTitleBar.titlePadding) * MAX_SUB_TITLE_PERCENT;
163  private isFollowingSystemFontScale: boolean = false;
164  private maxFontScale: number = 1;
165  private systemFontScale?: number = 1;
166  @Provide editableTitleBarTheme: EditableTitleBarTheme = new EditableTitleBarTheme();
167  @Prop contentMargin?: LocalizedMargin;
168  @State titleBarMargin: LocalizedMargin = {
169    start: LengthMetrics.resource($r('sys.float.margin_left')),
170    end: LengthMetrics.resource($r('sys.float.margin_right')),
171  }
172  @State fontSize: number = 1;
173
174  onWillApplyTheme(theme: Theme): void {
175    this.editableTitleBarTheme.iconColor = theme.colors.iconPrimary;
176    this.editableTitleBarTheme.titleColor = theme.colors.fontPrimary;
177    this.editableTitleBarTheme.subTitleColor = theme.colors.fontSecondary;
178    this.editableTitleBarTheme.iconBackgroundPressedColor = theme.colors.interactivePressed;
179    this.editableTitleBarTheme.iconBackgroundHoverColor = theme.colors.interactiveHover;
180    this.editableTitleBarTheme.iconBackgroundFocusOutlineColor = theme.colors.interactiveFocus;
181  }
182
183  aboutToAppear(): void {
184    try {
185      let uiContent: UIContext = this.getUIContext();
186      this.isFollowingSystemFontScale = uiContent.isFollowingSystemFontScale();
187      this.maxFontScale = uiContent.getMaxFontScale();
188    } catch (exception) {
189      let code: number = (exception as BusinessError).code;
190      let message: string = (exception as BusinessError).message;
191      hilog.error(0x3900, 'Ace', `Faild to init fontsizescale info,cause, code: ${code}, message: ${message}`);
192    }
193  }
194
195  decideFontScale(): number {
196    let uiContent: UIContext = this.getUIContext();
197    this.systemFontScale = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1;
198    if (!this.isFollowingSystemFontScale) {
199      return 1;
200    }
201    return Math.min(this.systemFontScale, this.maxFontScale);
202  }
203
204  build() {
205    Flex({
206      justifyContent: FlexAlign.SpaceBetween,
207      alignItems: ItemAlign.Stretch,
208    }) {
209      Row() {
210        Row() {
211          this.leftIconLayout();
212        }
213        .flexShrink(0)
214
215        Row() {
216          if (this.imageItem) {
217            Row() {
218              this.imageItemLayout();
219            }
220            .flexShrink(0)
221          }
222
223          Row() {
224            this.titleLayout();
225          }
226          .width('100%')
227          .flexShrink(1)
228        }
229        .width('100%')
230        .flexShrink(1)
231        .accessibilityGroup(true)
232        .accessibilityDescription($r('sys.string.subheader_accessibility_title'))
233
234        Row() {
235          this.rightMenuItemsLayout();
236        }
237        .flexShrink(0)
238      }
239      .width('100%')
240      .margin(this.contentMargin ?? this.titleBarMargin)
241      .height(EditableTitleBar.totalHeight)
242    }
243    .backgroundColor(this.options.backgroundColor ?? EditableTitleBar.noneColor)
244    .backgroundBlurStyle(
245      this.options.backgroundBlurStyle ?? BlurStyle.NONE)
246    .expandSafeArea(
247      this.options.safeAreaTypes ? this.options.safeAreaTypes : [SafeAreaType.SYSTEM],
248      this.options.safeAreaEdges ? this.options.safeAreaEdges : [SafeAreaEdge.TOP],
249    )
250  }
251
252  @Builder
253  imageItemLayout(): void {
254    ImageMenuItem({
255      item: this.imageItem,
256      attribute: ItemType.Image,
257    })
258  }
259
260  @Builder
261  leftIconLayout(): void {
262    if (this.leftIconStyle === EditableLeftIconType.Back) {
263      ImageMenuItem({
264        item: {
265          value: PUBLIC_BACK,
266          isEnabled: true,
267          action: () => this.onCancel ? this.onCancel() : this.getUIContext()?.getRouter()?.back(),
268          defaultFocus: this.leftIconDefaultFocus
269        },
270        fontSize: this.fontSize,
271        attribute: ItemType.LeftIcon,
272        imageMenuItemId: `BackMenuItem_${this.getUniqueId()}`
273      })
274    } else {
275      ImageMenuItem({
276        item: {
277          value: PUBLIC_CANCEL,
278          isEnabled: true,
279          action: () => this.onCancel && this.onCancel(),
280          defaultFocus: this.leftIconDefaultFocus
281        },
282        fontSize: this.fontSize,
283        attribute: ItemType.LeftIcon,
284        imageMenuItemId: `CancelMenuItem_${this.getUniqueId()}`
285      })
286    }
287  }
288
289  @Builder
290  titleLayout(): void {
291    Column() {
292      Row() {
293        Text(this.title)
294          .maxFontSize(TITLE_F_VP)
295          .minFontSize(SUBTITLE_F_VP)
296          .fontColor(this.editableTitleBarTheme.titleColor)
297          .maxLines(this.subtitle ? MAX_LINE_ONE : MAX_LINES_TWO)
298          .fontWeight(FontWeight.Bold)
299          .textAlign(TextAlign.Start)
300          .textOverflow({ overflow: TextOverflow.Ellipsis })
301          .heightAdaptivePolicy(this.subtitle ?
302          TextHeightAdaptivePolicy.MAX_LINES_FIRST : TextHeightAdaptivePolicy.MIN_FONT_SIZE_FIRST)
303          .constraintSize({
304            maxHeight: this.subtitle ? EditableTitleBar.maxMainTitleHeight : EditableTitleBar.totalHeight,
305          })
306      }
307      .justifyContent(FlexAlign.Start)
308
309      if (this.subtitle) {
310        Row() {
311          Text(this.subtitle)
312            .maxFontSize(SUBTITLE_F_VP)
313            .minFontSize(MIN_SUBTITLE_SIZE)
314            .fontColor(this.editableTitleBarTheme.subTitleColor)
315            .maxLines(MAX_LINE_ONE)
316            .fontWeight(FontWeight.Regular)
317            .textAlign(TextAlign.Start)
318            .textOverflow({ overflow: TextOverflow.Ellipsis })
319            .heightAdaptivePolicy(TextHeightAdaptivePolicy.MAX_LINES_FIRST)
320            .constraintSize({
321              maxHeight: this.title ? EditableTitleBar.maxSubTitleHeight : EditableTitleBar.totalHeight,
322            })
323        }
324        .margin({
325          top: $r('sys.float.padding_level1'),
326        })
327        .justifyContent(FlexAlign.Start)
328      }
329    }
330    .height(EditableTitleBar.totalHeight)
331    .justifyContent(FlexAlign.Center)
332    .margin({
333      // 'sys.float.titlebar_icon_background_space_horizontal' id,value: 8vp
334      start: LengthMetrics.resource($r('sys.float.titlebar_icon_background_space_horizontal')),
335    })
336    .alignItems(HorizontalAlign.Start)
337  }
338
339  @Builder
340  rightMenuItemsLayout(): void {
341    EditableTitleBarMenuSection({
342      menuItems: this.menuItems,
343      onSave: this.onSave,
344      isSaveEnabled: this.isSaveIconRequired,
345      fontSize: this.fontSize,
346      parentUniqueId: this.getUniqueId(),
347      saveIconDefaultFocus: this.saveIconDefaultFocus
348    })
349  }
350
351  onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Layoutable[], constraint: ConstraintSizeOptions): void {
352    children.forEach((child) => {
353      child.layout({ x: 0, y: 0 });
354    })
355  }
356
357  onMeasureSize(selfLayoutInfo: GeometryInfo, children: Measurable[], constraint: ConstraintSizeOptions): SizeResult {
358    let result: SizeResult = { width: selfLayoutInfo.width, height: selfLayoutInfo.height };
359    this.fontSize = this.decideFontScale();
360    children.forEach((child) => {
361      result.height = child.measure(constraint).height;
362      result.width = Number(constraint.maxWidth);
363    })
364    return result;
365  }
366}
367
368@Component
369struct EditableTitleBarMenuSection {
370  menuItems: Array<EditableTitleBarMenuItem> | undefined = undefined;
371  onSave?: () => void;
372  isSaveEnabled: boolean = true;
373  saveIconDefaultFocus?: boolean = false;
374  @Prop fontSize: number = 1;
375  @Prop parentUniqueId?: number;
376
377  build() {
378    Column() {
379      Row() {
380        if (this.menuItems !== undefined && this.menuItems.length > EditableTitleBar.commonZero) {
381          ForEach(this.menuItems.slice(EditableTitleBar.commonZero,
382            this.isSaveEnabled ?
383            EditableTitleBar.maxOtherCountOfExtraItems : EditableTitleBar.maxCountOfExtraItems),
384            (item: EditableTitleBarMenuItem, index: number) => {
385              ImageMenuItem({
386                item: item,
387                attribute: ItemType.Icon,
388                imageMenuItemId: `ImageMenuItem_${this.parentUniqueId}_${index}`
389              })
390            })
391        }
392        if (this.isSaveEnabled) {
393          ImageMenuItem({
394            item: {
395              value: PUBLIC_OK,
396              isEnabled: true,
397              action: () => this.onSave && this.onSave(),
398              defaultFocus: this.saveIconDefaultFocus
399            },
400            fontSize: this.fontSize,
401            attribute: ItemType.Icon,
402            imageMenuItemId: `SaveMenuItem_${this.parentUniqueId}`
403          })
404        }
405      }
406    }
407    .justifyContent(FlexAlign.Center)
408  }
409}
410
411@Component
412struct ImageMenuItem {
413  item: EditableTitleBarMenuItem = {
414    value: '',
415    isEnabled: true,
416    label: '',
417    accessibilityLevel: 'auto',
418    accessibilityText: '',
419    accessibilityDescription: '',
420  };
421  attribute: ItemType = ItemType.Image;
422  callbackId: number | undefined = undefined;
423  minFontSize: number = 1.75;
424  maxFontSize: number = 3.2;
425  longPressTime: number = 500;
426  systemFontScale?: number = 1;
427  isFollowingSystemFontScale: boolean = this.getUIContext().isFollowingSystemFontScale();;
428  maxFontScale: number = this.getUIContext().getMaxFontScale();
429  @State fontSize: number = 1;
430  @State isOnFocus: boolean = false;
431  @State isOnHover: boolean = false;
432  @State isOnClick: boolean = false;
433  imageMenuItemId?: string;
434  @Consume editableTitleBarTheme: EditableTitleBarTheme;
435  dialogController: CustomDialogController | null = new CustomDialogController({
436    builder: EditableTitleBarDialog({
437      cancel: () => {
438      },
439      confirm: () => {
440      },
441      itemEditableDialog: this.item,
442      textEditableTitleBarDialog: this.item.label ? this.item.label : this.textDialog(),
443      fontSize: this.fontSize,
444    }),
445    maskColor: Color.Transparent,
446    isModal: true,
447    customStyle: true,
448  });
449  @State buttonGestureModifier: ButtonGestureModifier = new ButtonGestureModifier(this.dialogController);
450
451  private textDialog(): ResourceStr {
452    if (this.item.value === PUBLIC_OK) {
453      return $r('sys.string.icon_save');
454    } else if (this.item.value === PUBLIC_CANCEL) {
455      return $r('sys.string.icon_cancel');
456    } else if (this.item.value === PUBLIC_BACK) {
457      return $r('sys.string.icon_back');
458    } else {
459      return this.item.label ? this.item.label : '';
460    }
461  }
462
463  aboutToAppear() {
464    try {
465      this.callbackId = getContext()?.getApplicationContext()?.on('environment', this.envCallback);
466    } catch (paramError) {
467      let code = (paramError as BusinessError).code;
468      let message = (paramError as BusinessError).message;
469      hilog.error(0x3900, 'Ace',
470        `EditableTitleBar Faild to get environment param error: ${code}, ${message}`);
471    }
472    this.fontSize = this.decideFontScale();
473    this.buttonGestureModifier.fontSize = this.fontSize;
474  }
475
476  private envCallback: EnvironmentCallback = {
477    onConfigurationUpdated: (config) => {
478      if (config === undefined || !this.isFollowingSystemFontScale) {
479        this.fontSize = 1;
480        return;
481      }
482      try {
483        this.fontSize = Math.min(
484          this.maxFontScale, config.fontSizeScale ?? 1);
485        this.buttonGestureModifier.fontSize = this.fontSize;
486      } catch (paramError) {
487        let code = (paramError as BusinessError).code;
488        let message = (paramError as BusinessError).message;
489        hilog.error(0x3900, 'Ace',
490          `EditableTitleBar environmentCallback error: ${code}, ${message}`);
491      }
492    },
493    onMemoryLevel: (level) => {
494    }
495  }
496
497  decideFontScale(): number {
498    try {
499      let uiContent: UIContext = this.getUIContext();
500      this.systemFontScale = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1;
501      if (!this.isFollowingSystemFontScale) {
502        return 1;
503      }
504      return Math.min(this.systemFontScale, this.maxFontScale);
505    } catch (exception) {
506      let code: number = (exception as BusinessError).code;
507      let message: string = (exception as BusinessError).message;
508      hilog.error(0x3900, 'EditableTitleBar', `Faild to decideFontScale,cause, code: ${code}, message: ${message}`);
509      return 1;
510    }
511  }
512
513  @Styles
514  buttonStateStyles() {
515    .stateStyles({
516      focused: this.focusedStyle,
517      normal: this.notInFocusedStyle,
518      pressed: this.notInFocusedStyle,
519    })
520  }
521
522  @Styles
523  focusedStyle() {
524    .border({
525      radius: $r('sys.float.titlebar_icon_background_shape'),
526      width: $r('sys.float.titlebar_icon_background_focus_outline_weight'),
527      color: this.editableTitleBarTheme.iconBackgroundFocusOutlineColor,
528      style: BorderStyle.Solid,
529    })
530  }
531
532  @Styles
533  notInFocusedStyle() {
534    .border({
535      radius: $r('sys.float.titlebar_icon_background_shape'),
536      width: EditableTitleBar.commonZero,
537    })
538  }
539
540  private touchEventAction(event: TouchEvent): void {
541    if (!this.item.isEnabled) {
542      return;
543    }
544    if (event.type === TouchType.Down) {
545      this.isOnClick = true;
546    }
547    if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
548      if (this.fontSize >= this.minFontSize) {
549        this.dialogController?.close()
550      }
551      this.isOnClick = false;
552    }
553  }
554
555  private keyEventAction(event: KeyEvent): void {
556    if (!this.item.isEnabled) {
557      return;
558    }
559    if (event.keyCode !== KeyCode.KEYCODE_ENTER && event.keyCode !== KeyCode.KEYCODE_SPACE) {
560      return;
561    }
562    if (event.type === KeyType.Down) {
563      this.isOnClick = true;
564    }
565    if (event.type === KeyType.Up) {
566      this.isOnClick = false;
567    }
568  }
569
570  @Styles
571  buttonEventStyle() {
572    .onFocus(() => {
573      if (!this.item.isEnabled) {
574        return;
575      }
576      this.isOnFocus = true;
577    })
578    .onBlur(() => this.isOnFocus = false)
579    .onHover((isOn) => {
580      if (!this.item.isEnabled) {
581        return;
582      }
583      this.isOnHover = isOn;
584    })
585    .onKeyEvent((event) => {
586      this.keyEventAction(event);
587    })
588    .onTouch((event) => {
589      this.touchEventAction(event);
590    })
591    .onClick(() => {
592      if (this.item.isEnabled === undefined) {
593        this.item.isEnabled = true;
594      }
595      this.item.isEnabled && this.item.action && this.item.action()
596    })
597  }
598
599  @Styles
600  backgroundButtonStyle() {
601    .width($r('sys.float.titlebar_icon_background_width'))
602    .height($r('sys.float.titlebar_icon_background_height'))
603    .focusable(this.item.isEnabled)
604    .enabled(this.item.isEnabled)
605  }
606
607  getBgColor(): ResourceColor {
608    if (this.isOnClick) {
609      return this.editableTitleBarTheme.iconBackgroundPressedColor;
610    } else if (this.isOnHover) {
611      return this.editableTitleBarTheme.iconBackgroundHoverColor;
612    } else {
613      return this.editableTitleBarTheme.iconBackgroundColor;
614    }
615  }
616
617  getFgColor(): ResourceStr {
618    if (this.isOnClick) {
619      return $r('sys.color.titlebar_icon_background_pressed_color');
620    } else if (this.isOnHover) {
621      return $r('sys.color.titlebar_icon_background_hover_color');
622    } else {
623      return EditableTitleBar.noneColor;
624    }
625  }
626
627  private toStringFormat(resource: ResourceStr | undefined): string | undefined {
628    if (typeof resource === 'string' || typeof resource === 'undefined') {
629      return resource;
630    } else {
631      let resourceString: string = '';
632      try {
633        if (resource.id === -1 ) {
634          resourceString = getContext()?.resourceManager?.getStringByNameSync(resource.params?.[0]?.split('.').pop()  ?? '');
635        } else {
636          resourceString = getContext()?.resourceManager?.getStringSync(resource);
637        }
638      } catch (err) {
639        let code: number = (err as BusinessError)?.code;
640        let message: string = (err as BusinessError)?.message;
641        hilog.error(0x3900, 'Ace', `Faild to EditableTitleBar toStringFormat, code: ${code}, message: ${message}`)
642      }
643      return resourceString;
644    }
645  }
646
647  private getAccessibilityReadText(): string | undefined {
648    if (this.item.value === PUBLIC_OK) {
649      return getContext()?.resourceManager?.getStringByNameSync('icon_save');
650    } else if (this.item.value === PUBLIC_CANCEL) {
651      return getContext()?.resourceManager?.getStringByNameSync('icon_cancel');
652    } else if (this.item.value === PUBLIC_BACK) {
653      return getContext()?.resourceManager?.getStringByNameSync('icon_back');
654    } else if (this.item.accessibilityText) {
655      return this.toStringFormat(this.item.accessibilityText);
656    } else if (this.item.label) {
657      return this.toStringFormat(this.item.label);
658    }
659    return ' ';
660  }
661
662  private getRightIconAccessibilityLevel(): string {
663    if (this.item.accessibilityLevel && this.item.accessibilityLevel !== '') {
664      return this.item.accessibilityLevel;
665    }
666    return 'auto';
667  }
668
669  private getAccessibilityDescription(): string | undefined {
670    if (this.item.accessibilityDescription && this.item.accessibilityDescription !== '') {
671      return this.toStringFormat(this.item.accessibilityDescription);
672    }
673    return '';
674  }
675
676  @Builder
677  IconBuilder(): void {
678    Button({ type: ButtonType.Normal, stateEffect: this.item.isEnabled }) {
679      if (this.item.symbolStyle !== undefined) {
680        SymbolGlyph()
681          .fontColor([this.editableTitleBarTheme.iconColor])
682          .attributeModifier(this.item.symbolStyle)
683          .focusable(this.item.isEnabled)
684          .enabled(this.item.isEnabled)
685          .draggable(false)
686          .accessibilityText(this.getAccessibilityReadText())
687          .effectStrategy(SymbolEffectStrategy.NONE)
688          .symbolEffect(new SymbolEffect(), false)
689          .fontSize(SYMBOL_SIZE)
690          .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false)
691      } else {
692        if (Util.isSymbolResource(this.item.value)) {
693          SymbolGlyph(this.item.value as Resource)
694            .fontSize(SYMBOL_SIZE)
695            .fontColor([this.editableTitleBarTheme.iconColor])
696            .focusable(this.item.isEnabled)
697            .enabled(this.item.isEnabled)
698            .draggable(false)
699            .accessibilityText(this.getAccessibilityReadText())
700            .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false)
701        } else {
702          Image(this.item.value)
703            .fillColor(this.editableTitleBarTheme.iconColor)
704            .matchTextDirection(this.item.value === PUBLIC_IMAGE_BACK ? true : false)
705            .width($r('sys.float.titlebar_icon_width'))
706            .height($r('sys.float.titlebar_icon_height'))
707            .focusable(this.item.isEnabled)
708            .enabled(this.item.isEnabled)
709            .draggable(false)
710            .accessibilityText(this.getAccessibilityReadText())
711            .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false)
712        }
713      }
714    }
715    .id(this.imageMenuItemId)
716    .backgroundButtonStyle()
717    .borderRadius($r('sys.float.titlebar_icon_background_shape'))
718    .margin({
719      start: this.attribute === ItemType.LeftIcon ? LengthMetrics.vp(EditableTitleBar.commonZero) :
720      LengthMetrics.resource($r('sys.float.titlebar_icon_background_space_horizontal')),
721    })
722    .focusOnTouch(true)
723    .foregroundColor(this.getFgColor())
724    .backgroundColor(this.getBgColor())
725    .buttonStateStyles()
726    .buttonEventStyle()
727    .gestureModifier(this.buttonGestureModifier)
728    .accessibilityLevel(this.getRightIconAccessibilityLevel())
729    .accessibilityDescription(this.getAccessibilityDescription())
730  }
731
732  @Builder
733  ImageBuilder() {
734    Stack({ alignContent: Alignment.Center }) {
735      Image(this.item.value)
736        .width($r('sys.float.titlebar_icon_background_width'))
737        .height($r('sys.float.titlebar_icon_background_height'))
738        .borderRadius($r('sys.float.corner_radius_level10'))
739        .focusable(false)
740        .enabled(this.item.isEnabled)
741        .objectFit(ImageFit.Cover)
742      Button({ type: ButtonType.Circle })
743        .backgroundButtonStyle()
744        .foregroundColor(this.getFgColor())
745        .backgroundColor(EditableTitleBar.noneColor)
746        .buttonStateStyles()
747        .buttonEventStyle()
748        .gestureModifier(this.buttonGestureModifier)
749        .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false)
750    }
751    .margin({
752      start: LengthMetrics.resource($r('sys.float.titlebar_icon_background_space_horizontal')),
753    })
754  }
755
756  build() {
757    if (this.attribute === ItemType.Icon || this.attribute === ItemType.LeftIcon) {
758      this.IconBuilder();
759    } else {
760      this.ImageBuilder();
761    }
762  }
763}
764
765/**
766 * EditableTitleBarDialog
767 *
768 * @since 2024-05-28
769 */
770@CustomDialog
771struct EditableTitleBarDialog {
772  itemEditableDialog: EditableTitleBarMenuItem = {
773    value: '',
774    isEnabled: true,
775  };
776  callbackId: number | undefined = undefined;
777  textEditableTitleBarDialog?: ResourceStr = '';
778  mainWindowStage: window.Window | undefined = undefined;
779  controller?: CustomDialogController
780  minFontSize: number = 1.75;
781  maxFontSize: number = 3.2;
782  screenWidth: number = 640;
783  verticalScreenLines: number = 6;
784  horizontalsScreenLines: number = 1;
785  cancel: () => void = () => {
786  }
787  confirm: () => void = () => {
788  }
789  @StorageLink('mainWindow') mainWindow: Promise<window.Window> | undefined = undefined;
790  @Prop fontSize: number = 1;
791  @State maxLines: number = 1;
792  @StorageProp('windowStandardHeight') windowStandardHeight: number = 0;
793
794  build() {
795    if (this.textEditableTitleBarDialog) {
796      Column() {
797        if (this.itemEditableDialog.symbolStyle !== undefined) {
798          SymbolGlyph()
799            .fontColor([$r('sys.color.icon_primary')])
800            .attributeModifier(this.itemEditableDialog.symbolStyle)
801            .margin({
802              top: $r('sys.float.padding_level24'),
803              bottom: $r('sys.float.padding_level8'),
804            })
805            .effectStrategy(SymbolEffectStrategy.NONE)
806            .symbolEffect(new SymbolEffect(), false)
807            .fontSize(SYMBOL_TITLE_SIZE)
808            .direction(Direction.Ltr)
809        } else {
810          if (Util.isSymbolResource(this.itemEditableDialog.value)) {
811            SymbolGlyph(this.itemEditableDialog.value as Resource)
812              .margin({
813                top: $r('sys.float.padding_level24'),
814                bottom: $r('sys.float.padding_level8'),
815              })
816              .fontColor([$r('sys.color.icon_primary')])
817              .fontSize(SYMBOL_TITLE_SIZE)
818              .direction(Direction.Ltr)
819          } else {
820            Image(this.itemEditableDialog.value)
821              .width(IMAGE_SIZE)
822              .height(IMAGE_SIZE)
823              .margin({
824                top: $r('sys.float.padding_level24'),
825                bottom: $r('sys.float.padding_level8'),
826              })
827              .fillColor($r('sys.color.icon_primary'))
828              .direction(Direction.Ltr)
829          }
830        }
831        Column() {
832          Text(this.textEditableTitleBarDialog)
833            .fontSize(TEXT_EDITABLE_DIALOG)
834            .textOverflow({ overflow: TextOverflow.Ellipsis })
835            .maxLines(this.maxLines)
836            .width('100%')
837            .textAlign(TextAlign.Center)
838            .fontColor($r('sys.color.font_primary'))
839        }
840        .width('100%')
841        .padding({
842          left: $r('sys.float.padding_level4'),
843          right: $r('sys.float.padding_level4'),
844          bottom: $r('sys.float.padding_level12'),
845        })
846      }
847      .width(this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG)
848      .constraintSize({ minHeight: this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG })
849      .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK)
850      .shadow(ShadowStyle.OUTER_DEFAULT_LG)
851      .borderRadius(($r('sys.float.corner_radius_level10')))
852    } else {
853      Column() {
854        if (this.itemEditableDialog.symbolStyle !== undefined) {
855          SymbolGlyph()
856            .fontColor([$r('sys.color.icon_primary')])
857            .attributeModifier(this.itemEditableDialog.symbolStyle)
858            .effectStrategy(SymbolEffectStrategy.NONE)
859            .symbolEffect(new SymbolEffect(), false)
860            .fontSize(SYMBOL_TITLE_SIZE)
861        } else {
862          if (Util.isSymbolResource(this.itemEditableDialog.value)) {
863            SymbolGlyph(this.itemEditableDialog.value as Resource)
864              .fontColor([$r('sys.color.icon_primary')])
865              .fontSize(SYMBOL_TITLE_SIZE)
866          } else {
867            Image(this.itemEditableDialog.value)
868              .width(IMAGE_SIZE)
869              .height(IMAGE_SIZE)
870              .fillColor($r('sys.color.icon_primary'))
871          }
872        }
873      }
874      .width(this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG)
875      .constraintSize({ minHeight: this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG })
876      .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK)
877      .shadow(ShadowStyle.OUTER_DEFAULT_LG)
878      .borderRadius(($r('sys.float.corner_radius_level10')))
879      .justifyContent(FlexAlign.Center)
880      .direction(Direction.Ltr)
881    }
882  }
883
884  async aboutToAppear(): Promise<void> {
885    let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
886    this.mainWindowStage = context.windowStage.getMainWindowSync();
887    let properties: window.WindowProperties = this.mainWindowStage.getWindowProperties();
888    let rect = properties.windowRect;
889    if (px2vp(rect.height) > this.screenWidth) {
890      this.maxLines = this.verticalScreenLines;
891    } else {
892      this.maxLines = this.horizontalsScreenLines;
893    }
894  }
895}
896
897/**
898 * get resource size
899 *
900 * @param resourceId resource id
901 * @return resource size
902 */
903function getNumberByResource(resourceId: number, defaultNumber: number): number {
904  try {
905    let resourceNumber: number = resourceManager.getSystemResourceManager().getNumber(resourceId);
906    if (resourceNumber === 0) {
907      return defaultNumber;
908    } else {
909      return resourceNumber;
910    }
911  } catch (error) {
912    let code: number = (error as BusinessError).code;
913    let message: string = (error as BusinessError).message;
914    hilog.error(0x3900, 'Ace', `EditableTitleBar getNumberByResource error, code: ${code},message:${message}`);
915    return 0;
916  }
917}
918
919class Util {
920  private static RESOURCE_TYPE_SYMBOL = 40000;
921
922  public static isSymbolResource(resourceStr: ResourceStr | undefined): boolean {
923    if (!Util.isResourceType(resourceStr)) {
924      return false;
925    }
926    let resource = resourceStr as Resource;
927    return resource.type === Util.RESOURCE_TYPE_SYMBOL;
928  }
929
930  public static isResourceType(resource: ResourceStr | Resource | undefined): boolean {
931    if (!resource) {
932      return false;
933    }
934    if (typeof resource === 'string' || typeof resource === 'undefined') {
935      return false;
936    }
937    return true;
938  }
939}
940