• 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, undefined, { disableSystemAdaptation: true })
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  getStringByNameSync(contextName: string): string {
628    let uiContext: string = '';
629    try {
630      uiContext = getContext()?.resourceManager?.getStringByNameSync(contextName);
631    } catch (exception) {
632      let code: number = (exception as BusinessError)?.code;
633      let message: string = (exception as BusinessError)?.message;
634      hilog.error(0x3900, 'Ace', `Faild to getStringByNameSync,cause, code: ${code}, message: ${message}`);
635    }
636    return uiContext;
637  }
638
639  private toStringFormat(resource: ResourceStr | undefined): string | undefined {
640    if (typeof resource === 'string' || typeof resource === 'undefined') {
641      return resource;
642    } else {
643      let resourceString: string = '';
644      try {
645        if (resource.id === -1 ) {
646          resourceString = getContext()?.resourceManager?.getStringByNameSync(resource.params?.[0]?.split('.').pop()  ?? '');
647        } else {
648          resourceString = getContext()?.resourceManager?.getStringSync(resource);
649        }
650      } catch (err) {
651        let code: number = (err as BusinessError)?.code;
652        let message: string = (err as BusinessError)?.message;
653        hilog.error(0x3900, 'Ace', `Faild to EditableTitleBar toStringFormat, code: ${code}, message: ${message}`)
654      }
655      return resourceString;
656    }
657  }
658
659  private getAccessibilityReadText(): string | undefined {
660    if (this.item.value === PUBLIC_OK) {
661      return this.getStringByNameSync('icon_save');
662    } else if (this.item.value === PUBLIC_CANCEL) {
663      return this.getStringByNameSync('icon_cancel');
664    } else if (this.item.value === PUBLIC_BACK) {
665      return this.getStringByNameSync('icon_back');
666    } else if (this.item.accessibilityText) {
667      return this.item.accessibilityText as string;
668    } else if (this.item.label) {
669      return this.item.label as string;
670    }
671    return ' ';
672  }
673
674  private getRightIconAccessibilityLevel(): string {
675    if (this.item.accessibilityLevel && this.item.accessibilityLevel !== '') {
676      return this.item.accessibilityLevel;
677    }
678    return 'auto';
679  }
680
681  private getAccessibilityDescription(): string | undefined {
682    if (this.item.accessibilityDescription && this.item.accessibilityDescription !== '') {
683      return this.item.accessibilityDescription as string;
684    }
685    return '';
686  }
687
688  @Builder
689  IconBuilder(): void {
690    Button({ type: ButtonType.Normal, stateEffect: this.item.isEnabled }) {
691      if (this.item.symbolStyle !== undefined) {
692        SymbolGlyph()
693          .fontColor([this.editableTitleBarTheme.iconColor])
694          .attributeModifier(this.item.symbolStyle)
695          .focusable(this.item.isEnabled)
696          .enabled(this.item.isEnabled)
697          .draggable(false)
698          .accessibilityText(this.getAccessibilityReadText())
699          .effectStrategy(SymbolEffectStrategy.NONE)
700          .symbolEffect(new SymbolEffect(), false)
701          .fontSize(SYMBOL_SIZE)
702          .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false)
703      } else {
704        if (Util.isSymbolResource(this.item.value)) {
705          SymbolGlyph(this.item.value as Resource)
706            .fontSize(SYMBOL_SIZE)
707            .fontColor([this.editableTitleBarTheme.iconColor])
708            .focusable(this.item.isEnabled)
709            .enabled(this.item.isEnabled)
710            .draggable(false)
711            .accessibilityText(this.getAccessibilityReadText())
712            .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false)
713        } else {
714          Image(this.item.value)
715            .fillColor(this.editableTitleBarTheme.iconColor)
716            .matchTextDirection(this.item.value === PUBLIC_IMAGE_BACK ? true : false)
717            .width($r('sys.float.titlebar_icon_width'))
718            .height($r('sys.float.titlebar_icon_height'))
719            .focusable(this.item.isEnabled)
720            .enabled(this.item.isEnabled)
721            .draggable(false)
722            .accessibilityText(this.getAccessibilityReadText())
723            .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false)
724        }
725      }
726    }
727    .id(this.imageMenuItemId)
728    .backgroundButtonStyle()
729    .borderRadius($r('sys.float.titlebar_icon_background_shape'))
730    .margin({
731      start: this.attribute === ItemType.LeftIcon ? LengthMetrics.vp(EditableTitleBar.commonZero) :
732      LengthMetrics.resource($r('sys.float.titlebar_icon_background_space_horizontal')),
733    })
734    .focusOnTouch(true)
735    .foregroundColor(this.getFgColor())
736    .backgroundColor(this.getBgColor())
737    .buttonStateStyles()
738    .buttonEventStyle()
739    .gestureModifier(this.buttonGestureModifier)
740    .accessibilityLevel(this.getRightIconAccessibilityLevel())
741    .accessibilityDescription(this.getAccessibilityDescription())
742  }
743
744  @Builder
745  ImageBuilder() {
746    Stack({ alignContent: Alignment.Center }) {
747      Image(this.item.value)
748        .width($r('sys.float.titlebar_icon_background_width'))
749        .height($r('sys.float.titlebar_icon_background_height'))
750        .borderRadius($r('sys.float.corner_radius_level10'))
751        .focusable(false)
752        .enabled(this.item.isEnabled)
753        .objectFit(ImageFit.Cover)
754      Button({ type: ButtonType.Circle })
755        .backgroundButtonStyle()
756        .foregroundColor(this.getFgColor())
757        .backgroundColor(EditableTitleBar.noneColor)
758        .buttonStateStyles()
759        .buttonEventStyle()
760        .gestureModifier(this.buttonGestureModifier)
761        .defaultFocus(this.item.isEnabled ? this.item.defaultFocus : false)
762    }
763    .margin({
764      start: LengthMetrics.resource($r('sys.float.titlebar_icon_background_space_horizontal')),
765    })
766  }
767
768  build() {
769    if (this.attribute === ItemType.Icon || this.attribute === ItemType.LeftIcon) {
770      this.IconBuilder();
771    } else {
772      this.ImageBuilder();
773    }
774  }
775}
776
777/**
778 * EditableTitleBarDialog
779 *
780 * @since 2024-05-28
781 */
782@CustomDialog
783struct EditableTitleBarDialog {
784  itemEditableDialog: EditableTitleBarMenuItem = {
785    value: '',
786    isEnabled: true,
787  };
788  callbackId: number | undefined = undefined;
789  textEditableTitleBarDialog?: ResourceStr = '';
790  mainWindowStage: window.Window | undefined = undefined;
791  controller?: CustomDialogController
792  minFontSize: number = 1.75;
793  maxFontSize: number = 3.2;
794  screenWidth: number = 640;
795  verticalScreenLines: number = 6;
796  horizontalsScreenLines: number = 1;
797  cancel: () => void = () => {
798  }
799  confirm: () => void = () => {
800  }
801  @StorageLink('mainWindow') mainWindow: Promise<window.Window> | undefined = undefined;
802  @Prop fontSize: number = 1;
803  @State maxLines: number = 1;
804  @StorageProp('windowStandardHeight') windowStandardHeight: number = 0;
805
806  build() {
807    if (this.textEditableTitleBarDialog) {
808      Column() {
809        if (this.itemEditableDialog.symbolStyle !== undefined) {
810          SymbolGlyph()
811            .fontColor([$r('sys.color.icon_primary')])
812            .attributeModifier(this.itemEditableDialog.symbolStyle)
813            .margin({
814              top: $r('sys.float.padding_level24'),
815              bottom: $r('sys.float.padding_level8'),
816            })
817            .effectStrategy(SymbolEffectStrategy.NONE)
818            .symbolEffect(new SymbolEffect(), false)
819            .fontSize(SYMBOL_TITLE_SIZE)
820            .direction(Direction.Ltr)
821        } else {
822          if (Util.isSymbolResource(this.itemEditableDialog.value)) {
823            SymbolGlyph(this.itemEditableDialog.value as Resource)
824              .margin({
825                top: $r('sys.float.padding_level24'),
826                bottom: $r('sys.float.padding_level8'),
827              })
828              .fontColor([$r('sys.color.icon_primary')])
829              .fontSize(SYMBOL_TITLE_SIZE)
830              .direction(Direction.Ltr)
831          } else {
832            Image(this.itemEditableDialog.value)
833              .width(IMAGE_SIZE)
834              .height(IMAGE_SIZE)
835              .margin({
836                top: $r('sys.float.padding_level24'),
837                bottom: $r('sys.float.padding_level8'),
838              })
839              .fillColor($r('sys.color.icon_primary'))
840              .direction(Direction.Ltr)
841          }
842        }
843        Column() {
844          Text(this.textEditableTitleBarDialog)
845            .fontSize(TEXT_EDITABLE_DIALOG)
846            .textOverflow({ overflow: TextOverflow.Ellipsis })
847            .maxLines(this.maxLines)
848            .width('100%')
849            .textAlign(TextAlign.Center)
850            .fontColor($r('sys.color.font_primary'))
851        }
852        .width('100%')
853        .padding({
854          left: $r('sys.float.padding_level4'),
855          right: $r('sys.float.padding_level4'),
856          bottom: $r('sys.float.padding_level12'),
857        })
858      }
859      .width(this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG)
860      .constraintSize({ minHeight: this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG })
861      .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK, undefined, { disableSystemAdaptation: true })
862      .shadow(ShadowStyle.OUTER_DEFAULT_LG)
863      .borderRadius(($r('sys.float.corner_radius_level10')))
864    } else {
865      Column() {
866        if (this.itemEditableDialog.symbolStyle !== undefined) {
867          SymbolGlyph()
868            .fontColor([$r('sys.color.icon_primary')])
869            .attributeModifier(this.itemEditableDialog.symbolStyle)
870            .effectStrategy(SymbolEffectStrategy.NONE)
871            .symbolEffect(new SymbolEffect(), false)
872            .fontSize(SYMBOL_TITLE_SIZE)
873        } else {
874          if (Util.isSymbolResource(this.itemEditableDialog.value)) {
875            SymbolGlyph(this.itemEditableDialog.value as Resource)
876              .fontColor([$r('sys.color.icon_primary')])
877              .fontSize(SYMBOL_TITLE_SIZE)
878          } else {
879            Image(this.itemEditableDialog.value)
880              .width(IMAGE_SIZE)
881              .height(IMAGE_SIZE)
882              .fillColor($r('sys.color.icon_primary'))
883          }
884        }
885      }
886      .width(this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG)
887      .constraintSize({ minHeight: this.fontSize === this.maxFontSize ? MAX_DIALOG : MIN_DIALOG })
888      .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK, undefined, { disableSystemAdaptation: true })
889      .shadow(ShadowStyle.OUTER_DEFAULT_LG)
890      .borderRadius(($r('sys.float.corner_radius_level10')))
891      .justifyContent(FlexAlign.Center)
892      .direction(Direction.Ltr)
893    }
894  }
895
896  async aboutToAppear(): Promise<void> {
897    try {
898      let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
899      this.mainWindowStage = context.windowStage.getMainWindowSync();
900    } catch (error) {
901      let code: number = (error as BusinessError)?.code;
902      let message: string = (error as BusinessError)?.message;
903      hilog.error(0x3900, 'Ace', `EditableTitleBar getMainWindowStage error, code: ${code},message:${message}`);
904      return 0;
905    }
906    let properties: window.WindowProperties = this.mainWindowStage.getWindowProperties();
907    let rect = properties.windowRect;
908    if (px2vp(rect.height) > this.screenWidth) {
909      this.maxLines = this.verticalScreenLines;
910    } else {
911      this.maxLines = this.horizontalsScreenLines;
912    }
913  }
914}
915
916/**
917 * get resource size
918 *
919 * @param resourceId resource id
920 * @return resource size
921 */
922function getNumberByResource(resourceId: number, defaultNumber: number): number {
923  try {
924    let resourceNumber: number = resourceManager.getSystemResourceManager().getNumber(resourceId);
925    if (resourceNumber === 0) {
926      return defaultNumber;
927    } else {
928      return resourceNumber;
929    }
930  } catch (error) {
931    let code: number = (error as BusinessError).code;
932    let message: string = (error as BusinessError).message;
933    hilog.error(0x3900, 'Ace', `EditableTitleBar getNumberByResource error, code: ${code},message:${message}`);
934    return 0;
935  }
936}
937
938class Util {
939  private static RESOURCE_TYPE_SYMBOL = 40000;
940
941  public static isSymbolResource(resourceStr: ResourceStr | undefined): boolean {
942    if (!Util.isResourceType(resourceStr)) {
943      return false;
944    }
945    let resource = resourceStr as Resource;
946    return resource.type === Util.RESOURCE_TYPE_SYMBOL;
947  }
948
949  public static isResourceType(resource: ResourceStr | Resource | undefined): boolean {
950    if (!resource) {
951      return false;
952    }
953    if (typeof resource === 'string' || typeof resource === 'undefined') {
954      return false;
955    }
956    return true;
957  }
958}
959