• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 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 { TextModifier, SymbolGlyphModifier } from '@ohos.arkui.modifier';
17import { Theme } from '@ohos.arkui.theme';
18import { ColorMetrics, LengthMetrics, LengthUnit } from '@ohos.arkui.node';
19import resourceManager from '@ohos.resourceManager';
20import { BusinessError } from '@ohos.base';
21import hilog from '@ohos.hilog';
22import common from '@ohos.app.ability.common';
23import { HashMap } from '@kit.ArkTS';
24import { KeyCode } from '@kit.InputKit';
25
26const INDEX_ZERO: number = 0;
27const INDEX_ONE: number = 1;
28const INDEX_TWO: number = 2;
29// 行数及整体高度
30const SINGLE_LINE_NUM: number = 1;
31const DOUBLE_LINE_NUM: number = 2;
32// 资源数值
33const RESOURCE_TYPE_SYMBOL: number = 40000;
34// 左边尺寸常量
35const LEFT_ICON_SIZE: ResourceStr = '16vp';
36const LEFT_ICON_SIZE_NUMBER: number = 16;
37const LEFT_TEXT_NUMBER: number = 8;
38// 右边尺寸常量
39const OPERATE_ITEM_LENGTH: number = 24;
40const ARROW_ICON_WIDTH: number = 12;
41const SINGLE_ICON_ZONE_SIZE: number = 28;
42const RIGHT_SINGLE_ICON_SIZE: ResourceStr = '24vp';
43const PADDING_LEVEL_2: number = 4;
44const MAX_RIGHT_WIDTH: Length = '34%';
45const MIN_FONT_SIZE: number = 1.75;
46const MIN_HOT_AREA_LENGTH: number = 40;
47const MULTI_ICON_REGION_WIDTH: number = 37;
48const ICON_REGION_X: number = -9;
49const ICON_REGION_Y: number = -6;
50const SINGLE_ICON_REGION_X: number = -12;
51const SINGLE_ICON_NUMBER: number = 1;
52const PADDING_LEFT: number = 2;
53
54export declare type SubHeaderV2IconType = ResourceStr | SymbolGlyphModifier;
55
56export interface SubHeaderV2TitleOptions {
57  primaryTitle?: ResourceStr;
58  primaryTitleModifier?: TextModifier;
59  secondaryTitle?: ResourceStr;
60  secondaryTitleModifier?: TextModifier;
61}
62
63@ObservedV2
64export class SubHeaderV2Title {
65  @Trace
66  public primaryTitle?: ResourceStr;
67  @Trace
68  public primaryTitleModifier?: TextModifier;
69  @Trace
70  public secondaryTitle?: ResourceStr;
71  @Trace
72  public secondaryTitleModifier?: TextModifier;
73
74  constructor(options: SubHeaderV2TitleOptions) {
75    this.primaryTitle = options.primaryTitle;
76    this.primaryTitleModifier = options.primaryTitleModifier;
77    this.secondaryTitle = options.secondaryTitle;
78    this.secondaryTitleModifier = options.secondaryTitleModifier;
79  }
80}
81
82export type SubHeaderV2SelectOnSelect = (selectedIndex: number, selectedContent?: string) => void;
83
84export interface SubHeaderV2SelectOptions {
85  options: SelectOption[];
86  selectedIndex?: number;
87  selectedContent?: string;
88  onSelect?: SubHeaderV2SelectOnSelect;
89  defaultFocus?: boolean;
90}
91
92@ObservedV2
93export class SubHeaderV2Select {
94  @Trace
95  public options: SelectOption[];
96  @Trace
97  public selectedIndex?: number;
98  @Trace
99  public selectedContent?: string;
100  @Trace
101  public onSelect?: SubHeaderV2SelectOnSelect;
102  @Trace
103  public defaultFocus?: boolean;
104
105  constructor(options: SubHeaderV2SelectOptions) {
106    this.options = options.options;
107    this.selectedIndex = options.selectedIndex;
108    this.selectedContent = options.selectedContent;
109    this.onSelect = options.onSelect;
110    this.defaultFocus = options.defaultFocus;
111  }
112}
113
114export enum SubHeaderV2OperationType {
115  TEXT_ARROW = 0,
116  BUTTON = 1,
117  ICON_GROUP = 2,
118  LOADING = 3,
119}
120
121export type SubHeaderV2OperationItemAction = () => void;
122
123declare type SubHeaderV2OperationItemType = ResourceStr | SymbolGlyphModifier;
124
125export interface SubHeaderV2OperationItemOptions {
126  content: SubHeaderV2OperationItemType;
127  action?: SubHeaderV2OperationItemAction;
128  accessibilityText?: ResourceStr;
129  accessibilityDescription?: ResourceStr;
130  accessibilityLevel?: string;
131  defaultFocus?: boolean;
132}
133
134@ObservedV2
135export class SubHeaderV2OperationItem {
136  @Trace
137  public content: SubHeaderV2IconType;
138  @Trace
139  public action?: SubHeaderV2OperationItemAction;
140  @Trace
141  public accessibilityText?: ResourceStr;
142  @Trace
143  public accessibilityDescription?: ResourceStr;
144  @Trace
145  public accessibilityLevel?: string;
146  @Trace
147  public defaultFocus?: boolean;
148
149  constructor(options: SubHeaderV2OperationItemOptions) {
150    this.content = options.content;
151    this.action = options.action;
152    this.accessibilityText = options.accessibilityText;
153    this.accessibilityDescription = options.accessibilityDescription;
154    this.accessibilityLevel = options.accessibilityLevel;
155    this.defaultFocus = options.defaultFocus;
156  }
157}
158
159@ObservedV2
160class ContentIconOption {
161  @Trace
162  public content?: ResourceStr;
163  @Trace
164  public subContent?: ResourceStr;
165  @Trace
166  public iconOptions?: SubHeaderV2IconType;
167  @Trace
168  public action?: () => void;
169  @Trace
170  public accessibilityLevel?: string;
171  @Trace
172  public accessibilityText?: ResourceStr;
173  @Trace
174  public accessibilityDescription?: ResourceStr;
175  @Trace
176  public defaultFocus?: boolean;
177}
178
179@ObservedV2
180class FontStyle {
181  @Trace
182  public maxLines: number = 0;
183  @Trace
184  public fontWeight: number = 0;
185  @Trace
186  public fontColor?: ResourceColor;
187  @Trace
188  public alignment?: Alignment;
189}
190
191@ObservedV2
192class SubHeaderTheme {
193  @Trace
194  public fontPrimaryColor: ResourceColor = $r('sys.color.font_primary');
195  @Trace
196  public fontSecondaryColor: ResourceColor = $r('sys.color.font_secondary');
197  @Trace
198  public fontButtonColor: ResourceColor = $r('sys.color.font_emphasize');
199  @Trace
200  public iconArrowColor: ResourceColor = $r('sys.color.icon_tertiary');
201  @Trace
202  public textArrowHoverBgColor: ResourceColor = $r('sys.color.interactive_hover');
203  @Trace
204  public borderFocusColor: ResourceColor = $r('sys.color.interactive_focus');
205  @Trace
206  public leftIconColor: ResourceColor = $r('sys.color.icon_secondary');
207  @Trace
208  public rightIconColor: ResourceColor = $r('sys.color.icon_primary');
209}
210
211@Extend(Text)
212function secondaryTitleStyles(fontStyle: FontStyle) {
213  .fontSize(`${getResourceValue('sys.float.Subtitle_S')}fp`)
214  .fontColor(fontStyle?.fontColor ?? $r('sys.color.font_secondary'))
215  .fontWeight(fontStyle?.fontWeight)
216  .maxLines(fontStyle?.maxLines)
217  .textOverflow({ overflow: TextOverflow.Ellipsis })
218  .align(fontStyle?.alignment)
219}
220
221@Extend(Text)
222function primaryTitleStyles(fontStyle: FontStyle) {
223  .fontSize(`${getResourceValue('sys.float.subheader_title_font_size')}fp`)
224  .fontColor(fontStyle?.fontColor ?? $r('sys.color.font_primary'))
225  .fontWeight(fontStyle?.fontWeight)
226  .maxLines(fontStyle?.maxLines)
227  .textOverflow({ overflow: TextOverflow.Ellipsis })
228  .align(fontStyle?.alignment)
229}
230
231@Styles
232function pressedStyle() {
233  .backgroundColor($r('sys.color.interactive_pressed'))
234}
235
236@Styles
237function disabledStyle() {
238  .opacity(getResourceValue('sys.float.interactive_disable'))
239}
240
241class SubHeaderModifier implements AttributeModifier<RowAttribute> {
242  public isAgeing: boolean = false
243
244  applyNormalAttribute(instance: RowAttribute): void {
245    if (this.isAgeing) {
246      instance.width('100%')
247    } else {
248    }
249  }
250}
251
252@ComponentV2
253export struct SubHeaderV2 {
254  @Param
255  icon?: SubHeaderV2IconType = undefined;
256  @Param
257  title?: SubHeaderV2Title | undefined = undefined;
258  @Param
259  select?: SubHeaderV2Select | undefined = undefined;
260  @Param
261  operationType?: SubHeaderV2OperationType = SubHeaderV2OperationType.BUTTON;
262  @Param
263  operationItems?: SubHeaderV2OperationItem[] = undefined;
264  @BuilderParam titleBuilder?: () => void;
265  @Local fontSize: number = 1;
266  @Local ageing: boolean = true;
267  // 内部变量
268  @Local textArrowBgColor: ResourceColor = $r('sys.color.ohos_id_color_sub_background_transparent');
269  @Local buttonBgColor: ResourceColor = $r('sys.color.ohos_id_color_sub_background_transparent');
270  @Local selectedIndex: number | Resource | undefined = -1;
271  @Local selectedContent: ResourceStr | undefined = '';
272  @Local symbolWidth: number = LEFT_ICON_SIZE_NUMBER + LEFT_TEXT_NUMBER;
273  @Local subHeaderModifier: SubHeaderModifier = new SubHeaderModifier();
274  isFollowingSystemFontScale: boolean = false;
275  appMaxFontScale: number = 3.2;
276  @Provider('subHeaderV2Theme') subHeaderV2Theme: SubHeaderTheme = new SubHeaderTheme();
277  subHeaderMargin: LocalizedMargin = {
278    start: LengthMetrics.vp(getResourceValue('sys.float.margin_left')),
279    end: LengthMetrics.vp(getResourceValue('sys.float.margin_right')),
280  };
281
282  onWillApplyTheme(theme: Theme) {
283    this.subHeaderV2Theme.fontPrimaryColor = theme.colors.fontPrimary;
284    this.subHeaderV2Theme.fontSecondaryColor = theme.colors.fontSecondary;
285    this.subHeaderV2Theme.fontButtonColor = theme.colors.fontEmphasize;
286    this.subHeaderV2Theme.iconArrowColor = theme.colors.iconTertiary;
287    this.subHeaderV2Theme.textArrowHoverBgColor = theme.colors.interactiveHover;
288    this.subHeaderV2Theme.borderFocusColor = theme.colors.interactiveFocus;
289    this.subHeaderV2Theme.leftIconColor = theme.colors.iconSecondary;
290    this.subHeaderV2Theme.rightIconColor = theme.colors.iconPrimary;
291  }
292
293  async aboutToAppear(): Promise<void> {
294    let uiContext: UIContext = this.getUIContext();
295    this.isFollowingSystemFontScale = uiContext.isFollowingSystemFontScale();
296    this.appMaxFontScale = uiContext.getMaxFontScale();
297    this.fontSize = this.updateFontScale();
298    if (this.isSuitableAging()) {
299      this.ageing = true;
300      this.subHeaderModifier.isAgeing = this.ageing;
301    } else {
302      this.ageing = false;
303      this.subHeaderModifier.isAgeing = this.ageing;
304    }
305    if (this.select) {
306      this.selectedIndex = this.select.selectedIndex;
307      this.selectedContent = this.select.selectedContent;
308    }
309  }
310
311  updateFontScale(): number {
312    try {
313      let uiContext: UIContext = this.getUIContext();
314      let systemFontScale: number = (uiContext.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1;
315      if (!this.isFollowingSystemFontScale) {
316        return 1;
317      }
318      return Math.min(systemFontScale, this.appMaxFontScale);
319    } catch (exception) {
320      let code: number = (exception as BusinessError).code;
321      let message: string = (exception as BusinessError).message;
322      hilog.error(0x3900, 'Ace', `Faild to init fontsizescale info,cause, code: ${code}, message: ${message}`);
323      return 1;
324    }
325  }
326
327  @Builder
328  IconSecondaryTitleStyle($$: ContentIconOption): void {
329    Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
330      if (Util.isSymbolResource($$.iconOptions)) {
331        SymbolGlyphChild({
332          icon: $$.iconOptions,
333          fontColor: [this.subHeaderV2Theme.leftIconColor],
334          changeSymbolWidth: (result) => {
335            this.symbolWidth = result.width;
336          }
337        })
338      } else {
339        Image($$.iconOptions as ResourceStr)
340          .fillColor(this.subHeaderV2Theme.leftIconColor)
341          .width(LEFT_ICON_SIZE)
342          .height(LEFT_ICON_SIZE)
343          .margin({ end: LengthMetrics.vp(getResourceValue('sys.float.padding_level4')) })
344          .draggable(false)
345          .flexShrink(0)
346      }
347      Text($$.content)
348        .secondaryTitleStyles({
349          maxLines: DOUBLE_LINE_NUM,
350          fontWeight: FontWeight.Medium,
351          alignment: Alignment.Start,
352          fontColor: this.subHeaderV2Theme.fontSecondaryColor,
353        })
354        .attributeModifier(this.title?.secondaryTitleModifier)
355        .flexShrink(1)
356    }
357    .commonListPadding()
358  }
359
360  @Styles
361  private commonContentPadding() {
362    .padding({
363      end: LengthMetrics.vp(getResourceValue('sys.float.padding_level0')),
364      top: this.fontSize >= MIN_FONT_SIZE ? LengthMetrics.vp(getResourceValue('sys.float.padding_level0'))
365        : LengthMetrics.vp(getResourceValue('sys.float.padding_level4')),
366      bottom: this.fontSize >= MIN_FONT_SIZE ? LengthMetrics.vp(getResourceValue('sys.float.padding_level0'))
367        : LengthMetrics.vp(getResourceValue('sys.float.padding_level4')),
368    })
369  }
370
371  @Styles
372  private commonListPadding() {
373    .padding({
374      end: LengthMetrics.vp(getResourceValue('sys.float.padding_level6')),
375      top: this.fontSize >= MIN_FONT_SIZE ? LengthMetrics.vp(getResourceValue('sys.float.padding_level0'))
376        : LengthMetrics.vp(getResourceValue('sys.float.padding_level4')),
377      bottom: this.fontSize >= MIN_FONT_SIZE ? LengthMetrics.vp(getResourceValue('sys.float.padding_level0'))
378        : LengthMetrics.vp(getResourceValue('sys.float.padding_level4')),
379    })
380  }
381
382  private isSuitableAging(): boolean | null {
383    return (this.fontSize >= MIN_FONT_SIZE) && ((this.operationType === SubHeaderV2OperationType.TEXT_ARROW) ||
384      this.operationType === SubHeaderV2OperationType.BUTTON) && this.operationItems !== undefined &&
385      (this.operationItems?.length > 0) && this.operationItems[0].content !== '';
386  }
387
388  private isLeftAreaAccessibilityGroup(): boolean {
389    if (this.titleBuilder || this.title?.secondaryTitle) {
390      return true;
391    }
392    if (this.select) {
393      return false;
394    }
395    return true;
396  }
397
398  @Builder
399  SelectStyle(selectParam: SubHeaderV2Select): void {
400    Select(selectParam.options)
401      .height('auto')
402      .width('auto')
403      .selected(this.selectedIndex)
404      .value(this.selectedContent)
405      .defaultFocus(this.select?.defaultFocus)
406      .onSelect((index: number, value?: string) => {
407        this.selectedIndex = index;
408        if (value) {
409          this.selectedContent = value;
410        }
411        if (selectParam.onSelect) {
412          selectParam.onSelect(index, value);
413        }
414      })
415      .font({
416        size: `${getResourceValue('sys.float.Body_L')}fp`,
417        weight: FontWeight.Medium,
418      })
419  }
420
421  @Builder
422  SubTitleStyle($$: ContentIconOption): void {
423    Column() {
424      Text($$.content)
425        .primaryTitleStyles({
426          fontWeight: getResourceValue('sys.float.subheader_title_font_weight'),
427          maxLines: DOUBLE_LINE_NUM,
428          alignment: Alignment.Start,
429          fontColor: this.subHeaderV2Theme.fontPrimaryColor,
430        })
431        .attributeModifier(this.title?.primaryTitleModifier)
432      Text($$.subContent)
433        .secondaryTitleStyles({
434          maxLines: DOUBLE_LINE_NUM,
435          fontWeight: FontWeight.Regular,
436          alignment: Alignment.Start,
437          fontColor: this.subHeaderV2Theme.fontSecondaryColor,
438        })
439        .margin({
440          top: getResourceValue('sys.float.padding_level1'),
441        })
442        .attributeModifier(this.title?.secondaryTitleModifier)
443    }
444    .width('100%')
445    .commonContentPadding()
446    .alignItems(HorizontalAlign.Start)
447  }
448
449  @Builder
450  SecondTitleStyle($$: ContentIconOption): void {
451    Text($$.content)
452      .secondaryTitleStyles({
453        maxLines: DOUBLE_LINE_NUM,
454        fontWeight: FontWeight.Medium,
455        alignment: Alignment.Start,
456        fontColor: this.subHeaderV2Theme.fontSecondaryColor,
457      })
458      .attributeModifier(this.title?.secondaryTitleModifier)
459      .commonListPadding()
460  }
461
462  @Builder
463  PrimaryTitleStyle($$: ContentIconOption): void {
464    Text($$.content)
465      .primaryTitleStyles({
466        fontWeight: getResourceValue('sys.float.subheader_title_font_weight'),
467        maxLines: DOUBLE_LINE_NUM,
468        alignment: Alignment.Start,
469        fontColor: this.subHeaderV2Theme.fontPrimaryColor,
470      })
471      .attributeModifier(this.title?.primaryTitleModifier)
472      .commonContentPadding()
473  }
474
475  @Builder
476  leftArea(): void {
477    if (this.titleBuilder) {
478      this.titleBuilder();
479    } else if (this.title?.secondaryTitle && this.icon) {
480      this.IconSecondaryTitleStyle({
481        content: this.title?.secondaryTitle,
482        iconOptions: this.icon,
483      });
484    } else if (this.title?.secondaryTitle && this.title?.primaryTitle) {
485      this.SubTitleStyle({ content: this.title?.primaryTitle, subContent: this.title?.secondaryTitle });
486    } else if (this.title?.secondaryTitle) {
487      this.SecondTitleStyle({ content: this.title?.secondaryTitle });
488    } else if (this.select) {
489      this.SelectStyle(this.select);
490    } else if (this.title?.primaryTitle) {
491      this.PrimaryTitleStyle({ content: this.title?.primaryTitle });
492    } else {
493      // 其他不支持场景
494      this.dummyFunction();
495    }
496  }
497
498  private isRightAreaExists(): boolean {
499    if (this.operationItems && this.operationItems.length > 0) {
500      return true;
501    }
502    if (this.operationType === SubHeaderV2OperationType.LOADING) {
503      return true;
504    }
505    return false;
506  }
507
508  @Styles
509  private rightAreaClickEvent() {
510    .onKeyEvent((event: KeyEvent) => {
511      if (!event) {
512        return;
513      }
514      if ((event.keyCode === KeyCode.KEYCODE_SPACE || event.keyCode === KeyCode.KEYCODE_ENTER) &&
515        event.type === KeyType.Down) {
516        if ((this.operationType === SubHeaderV2OperationType.TEXT_ARROW ||
517          this.operationType === SubHeaderV2OperationType.BUTTON) &&
518        this.operationItems && this.operationItems.length > 0 && this.operationItems[0].action) {
519          this.operationItems[0].action();
520        }
521        event.stopPropagation();
522      }
523    })
524    .onClick(() => {
525      if ((this.operationType === SubHeaderV2OperationType.TEXT_ARROW ||
526        this.operationType === SubHeaderV2OperationType.BUTTON) &&
527      this.operationItems && this.operationItems.length > 0 && this.operationItems[0].action) {
528        this.operationItems[0].action();
529      }
530    })
531    .onTouch((event) => {
532      if (event.type === TouchType.Down) {
533        if (this.operationType === SubHeaderV2OperationType.TEXT_ARROW) {
534          this.textArrowBgColor = $r('sys.color.interactive_pressed');
535        }
536        if (this.operationType === SubHeaderV2OperationType.BUTTON) {
537          this.buttonBgColor = $r('sys.color.interactive_pressed');
538        }
539      }
540      if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
541        if (this.operationType === SubHeaderV2OperationType.TEXT_ARROW) {
542          this.textArrowBgColor = $r('sys.color.ohos_id_color_sub_background_transparent');
543        }
544        if (this.operationType === SubHeaderV2OperationType.BUTTON) {
545          this.buttonBgColor = $r('sys.color.ohos_id_color_sub_background_transparent');
546        }
547      }
548    })
549  }
550
551  private getRightAreaMaxWidth(): Length {
552    if (this.operationType === SubHeaderV2OperationType.ICON_GROUP &&
553      (this.operationItems && this.operationItems.length > 0)) {
554      return '100%';
555    }
556    return MAX_RIGHT_WIDTH;
557  }
558
559  private getRightAreaMinWidth(): Length {
560    if (this.operationItems && this.operationItems.length > 0) {
561      return MIN_HOT_AREA_LENGTH;
562    }
563    return 0;
564  }
565
566  @Styles
567  private rightAreaParentStyles() {
568    .constraintSize({
569      maxWidth: this.getRightAreaMaxWidth(),
570      minWidth: this.getRightAreaMinWidth(),
571      minHeight: MIN_HOT_AREA_LENGTH,
572    })
573    .flexShrink(0)
574    .accessibilityLevel(this.operationType === SubHeaderV2OperationType.BUTTON ||
575      this.operationType === SubHeaderV2OperationType.TEXT_ARROW ?
576    this.getRightAreaAccessibilityLevel() : 'no')
577  }
578
579  private getAccessibilityDescription(): string | undefined {
580    if (!this.operationItems || this.operationItems.length <= 0) {
581      return '';
582    }
583    if (this.operationItems[0]?.accessibilityDescription && this.operationItems[0]?.accessibilityDescription !== '') {
584      return this.operationItems[0]?.accessibilityDescription as string;
585    }
586    return '';
587  }
588
589  private leftIconMargin(): LengthMetrics {
590    if (this.titleBuilder) {
591      return LengthMetrics.vp(0);
592    }
593    if (this.icon && Util.isSymbolResource(this.icon)) {
594      return this.ageing ?
595      LengthMetrics.vp(this.symbolWidth) :
596      LengthMetrics.vp(0);
597    } else {
598      return (this.ageing && this.icon) ? LengthMetrics.vp(LEFT_ICON_SIZE_NUMBER +
599        LEFT_TEXT_NUMBER) : LengthMetrics.vp(0);
600    }
601  }
602
603  onMeasureSize(selfLayoutInfo: GeometryInfo, children: Measurable[], constraint: ConstraintSizeOptions): SizeResult {
604    let result: SizeResult = { width: selfLayoutInfo.width, height: selfLayoutInfo.height };
605    let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
606    this.fontSize = this.updateFontScale();
607    if (this.isSuitableAging()) {
608      this.ageing = true;
609      this.subHeaderModifier.isAgeing = this.ageing;
610    } else {
611      this.ageing = false;
612      this.subHeaderModifier.isAgeing = this.ageing;
613    }
614    children.forEach((child) => {
615      constraint.minHeight = Math.min(Number(this.getMinHeight()), Number(constraint.maxHeight));
616      result.height = child.measure(constraint).height;
617      result.width = Number(constraint.maxWidth);
618    })
619    return result;
620  }
621
622  @Builder
623  ButtonStyle(): void {
624    if (this.operationItems) {
625      Button({ type: ButtonType.Normal, stateEffect: false }) {
626        Text(this.operationItems[0].content as ResourceStr)
627          .secondaryTitleStyles({
628            fontWeight: FontWeight.Medium,
629            maxLines: DOUBLE_LINE_NUM,
630            fontColor: this.subHeaderV2Theme.fontButtonColor,
631          })
632          .defaultFocus(this.operationItems[0].defaultFocus)
633          .focusable(true)
634      }
635      .focusable(true)
636      .focusBox({
637        margin: { value: INDEX_ZERO, unit: LengthUnit.VP },
638        strokeColor: ColorMetrics.resourceColor(this.subHeaderV2Theme.borderFocusColor),
639        strokeWidth: LengthMetrics.vp(getResourceValue('sys.float.outline_extra_larger')),
640      })
641      .padding({
642        start: LengthMetrics.vp(getResourceValue('sys.float.padding_level1')),
643        end: LengthMetrics.vp(getResourceValue('sys.float.padding_level1')),
644        top: LengthMetrics.vp(getResourceValue('sys.float.padding_level2')),
645        bottom: LengthMetrics.vp(getResourceValue('sys.float.padding_level2')),
646      })
647      .margin({
648        start: this.ageing ?
649        LengthMetrics.vp(LengthMetrics.vp(getResourceValue('sys.float.padding_level0')).value +
650        this.leftIconMargin().value) :
651        LengthMetrics.vp(LengthMetrics.vp(getResourceValue('sys.float.padding_level4')).value +
652        this.leftIconMargin().value),
653        bottom: LengthMetrics.vp(this.ageing ? getResourceValue('sys.float.padding_level0') :
654        getResourceValue('sys.float.padding_level2')),
655      })
656      .backgroundColor(this.buttonBgColor)
657      .constraintSize({ minHeight: OPERATE_ITEM_LENGTH })
658      .align(Alignment.End)
659      .borderRadius(getResourceValue('sys.float.corner_radius_level4'))
660      .onHover((isHover: boolean) => {
661        if (isHover) {
662          this.buttonBgColor = this.subHeaderV2Theme.textArrowHoverBgColor;
663        } else {
664          this.buttonBgColor = $r('sys.color.ohos_id_color_sub_background_transparent');
665        }
666      })
667      .stateStyles({
668        pressed: pressedStyle,
669        disabled: disabledStyle,
670      })
671    }
672  }
673
674  private getTextArrowPaddingLeft(): LengthMetrics {
675    if (this.operationItems && this.operationItems.length > 0 && this.operationItems[0].content) {
676      return LengthMetrics.vp(getResourceValue('sys.float.padding_level1'));
677    }
678    return LengthMetrics.vp(getResourceValue('sys.float.padding_level0'));
679  }
680
681  private getTextArrowMarginRight(): LengthMetrics {
682    if (this.operationItems && this.operationItems.length > 0 && this.operationItems[0].content) {
683      return LengthMetrics.vp(PADDING_LEVEL_2 + ARROW_ICON_WIDTH);
684    }
685    return LengthMetrics.vp(ARROW_ICON_WIDTH);
686  }
687
688  @Builder
689  TextStyle(): void {
690    Row() {
691      if (this.operationItems?.[0]) {
692        Text(this.operationItems[0].content as ResourceStr)
693          .secondaryTitleStyles({
694            maxLines: DOUBLE_LINE_NUM,
695            fontWeight: FontWeight.Regular,
696            alignment: Alignment.End,
697            fontColor: this.subHeaderV2Theme.fontSecondaryColor,
698          })
699          .focusable(true)
700          .defaultFocus(this.operationItems[0].defaultFocus)
701          .margin({
702            end: this.getTextArrowMarginRight(),
703          })
704      }
705    }
706    .attributeModifier(this.subHeaderModifier)
707    .alignItems(VerticalAlign.Center)
708    .focusable(true)
709    .constraintSize({ minHeight: OPERATE_ITEM_LENGTH })
710    .padding({
711      start: this.getTextArrowPaddingLeft(),
712      top: this.ageing ? LengthMetrics.vp(0) : LengthMetrics.vp(getResourceValue('sys.float.padding_level2')),
713      bottom: this.ageing ? LengthMetrics.vp(0) : LengthMetrics.vp(getResourceValue('sys.float.padding_level2')),
714    })
715  }
716
717  @Builder
718  ArrowStyle(): void {
719    Row() {
720      SymbolGlyph($r('sys.symbol.chevron_right'))
721        .fontSize(RIGHT_SINGLE_ICON_SIZE)
722        .fontColor([this.subHeaderV2Theme.iconArrowColor])
723        .draggable(false)
724        .width(ARROW_ICON_WIDTH)
725        .height(OPERATE_ITEM_LENGTH)
726    }
727    .justifyContent(FlexAlign.End)
728  }
729
730  @Builder
731  TextArrowStyle(): void {
732    if (this.operationItems?.[0] && this.operationItems[0] && this.operationItems[0].content &&
733      this.operationItems[0].content.toString().length > 0) {
734      Stack() {
735        Button({ type: ButtonType.Normal, stateEffect: false }) {
736          TextArrowLayout() {
737            ForEach([INDEX_ZERO, INDEX_ONE], (index: number) => {
738              if (index === INDEX_ZERO) {
739                this.TextStyle();
740              } else {
741                this.ArrowStyle();
742              }
743            });
744          }
745        }
746        .padding(INDEX_ZERO)
747        .margin({ start: this.leftIconMargin() })
748        .backgroundColor(this.textArrowBgColor)
749        .focusBox({
750          margin: { value: INDEX_ZERO, unit: LengthUnit.VP },
751          strokeColor: ColorMetrics.resourceColor(this.subHeaderV2Theme.borderFocusColor),
752          strokeWidth: LengthMetrics.vp(getResourceValue('sys.float.outline_extra_larger')),
753        })
754        .borderRadius(getResourceValue('sys.float.corner_radius_level4'))
755        .stateStyles({
756          pressed: pressedStyle,
757          disabled: disabledStyle,
758        })
759        .onHover((isHover: boolean) => {
760          if (isHover) {
761            this.textArrowBgColor = this.subHeaderV2Theme.textArrowHoverBgColor;
762          } else {
763            this.textArrowBgColor = $r('sys.color.ohos_id_color_sub_background_transparent');
764          }
765        })
766      }
767      .focusable(true)
768      .align(this.ageing ? Alignment.Start : Alignment.End)
769      .margin({
770        start: LengthMetrics.vp(this.ageing ? getResourceValue('sys.float.padding_level0') :
771        getResourceValue('sys.float.padding_level4')),
772        bottom: LengthMetrics.vp(this.ageing ? getResourceValue('sys.float.padding_level0') :
773        getResourceValue('sys.float.padding_level2')),
774      })
775    } else {
776      Row() {
777        Button({ type: ButtonType.Normal, stateEffect: false }) {
778          SymbolGlyph($r('sys.symbol.chevron_right'))
779            .fontSize(RIGHT_SINGLE_ICON_SIZE)
780            .fontColor([this.subHeaderV2Theme.iconArrowColor])
781            .draggable(false)
782            .focusable(true)
783            .width(ARROW_ICON_WIDTH)
784            .height(OPERATE_ITEM_LENGTH)
785        }
786        .width(ARROW_ICON_WIDTH)
787        .height(OPERATE_ITEM_LENGTH)
788        .backgroundColor(this.textArrowBgColor)
789        .focusBox({
790          margin: { value: INDEX_ZERO, unit: LengthUnit.VP },
791          strokeColor: ColorMetrics.resourceColor(this.subHeaderV2Theme.borderFocusColor),
792          strokeWidth: LengthMetrics.vp(getResourceValue('sys.float.outline_extra_larger')),
793        })
794        .borderRadius(getResourceValue('sys.float.corner_radius_level4'))
795        .stateStyles({
796          pressed: pressedStyle,
797          disabled: disabledStyle,
798        })
799        .onHover((isHover: boolean) => {
800          if (isHover) {
801            this.textArrowBgColor = this.subHeaderV2Theme.textArrowHoverBgColor;
802          } else {
803            this.textArrowBgColor = $r('sys.color.ohos_id_color_sub_background_transparent');
804          }
805        })
806        .focusable(true)
807        .margin({
808          start: LengthMetrics.vp(this.ageing ? getResourceValue('sys.float.padding_level0') :
809          getResourceValue('sys.float.padding_level4')),
810          bottom: LengthMetrics.vp(this.ageing ? getResourceValue('sys.float.padding_level0') :
811          getResourceValue('sys.float.padding_level2')),
812        })
813      }
814      .focusable(true)
815      .constraintSize({ minWidth: this.getRightAreaMinWidth() })
816      .justifyContent(FlexAlign.End)
817    }
818  }
819
820  @Builder
821  IconGroupStyle(): void {
822    Row() {
823      ForEach(this.operationItems, (item: SubHeaderV2OperationItem, index: number) => {
824        if (index <= INDEX_TWO) {
825          SingleIconStyle({
826            item: {
827              iconOptions: this.operationItems?.[index].content,
828              action: this.operationItems?.[index].action,
829              defaultFocus: this.operationItems?.[index].defaultFocus,
830              accessibilityLevel: this.operationItems?.[index].accessibilityLevel,
831              accessibilityText: this.operationItems?.[index].accessibilityText,
832              accessibilityDescription: this.operationItems?.[index].accessibilityDescription,
833            },
834            isSingleIcon: this.operationItems?.length === SINGLE_ICON_NUMBER,
835          })
836            .margin({
837              start: LengthMetrics.vp(getResourceValue('sys.float.padding_level4')),
838              bottom: LengthMetrics.vp(getResourceValue('sys.float.padding_level3')),
839            })
840        } else {
841          // 最大支持3个ICON,此场景不支持
842        }
843      }, (item: SubHeaderV2OperationItem, index: number) => {
844        return `${index}`;
845      })
846    }
847    .justifyContent(FlexAlign.End)
848    .focusable(true)
849  }
850
851  @Builder
852  rightArea(): void {
853    if (this.operationType === SubHeaderV2OperationType.BUTTON &&
854      (this.operationItems && this.operationItems.length > 0)) {
855      this.ButtonStyle();
856    }
857    if (this.operationType === SubHeaderV2OperationType.TEXT_ARROW &&
858      (this.operationItems && this.operationItems.length > 0)) {
859      this.TextArrowStyle();
860    }
861    if (this.operationType === SubHeaderV2OperationType.ICON_GROUP &&
862      (this.operationItems && this.operationItems.length > 0)) {
863      this.IconGroupStyle();
864    }
865    if (this.operationType === SubHeaderV2OperationType.LOADING) {
866      this.LoadingProcessStyle();
867    }
868    if (this.operationType === undefined && (this.operationItems && this.operationItems.length > 0)) {
869      this.ButtonStyle();
870    }
871  }
872
873  @Builder
874  rightAreaParent(): void {
875    if (this.operationType === SubHeaderV2OperationType.BUTTON ||
876      this.operationType === SubHeaderV2OperationType.TEXT_ARROW) {
877      Button({ type: ButtonType.Normal, stateEffect: false }) {
878        this.rightArea();
879      }
880      .focusable(this.operationItems ? true : false)
881      .margin(INDEX_ZERO)
882      .padding(INDEX_ZERO)
883      .align(Alignment.BottomEnd)
884      .rightAreaClickEvent()
885      .rightAreaParentStyles()
886      .hoverEffect(HoverEffect.None)
887      .backgroundColor($r('sys.color.ohos_id_color_sub_background_transparent'))
888      .accessibilityGroup(true)
889      .accessibilityText(this.getRightAreaAccessibilityText())
890      .accessibilityDescription(this.getAccessibilityDescription())
891    } else {
892      Row() {
893        this.rightArea();
894      }
895      .focusable(this.operationItems && this.operationType !== SubHeaderV2OperationType.LOADING ? true : false)
896      .justifyContent(FlexAlign.End)
897      .alignItems(VerticalAlign.Bottom)
898      .rightAreaClickEvent()
899      .rightAreaParentStyles()
900    }
901  }
902
903  @Builder
904  rightAreaParentAging(): void {
905    if (this.operationType === SubHeaderV2OperationType.BUTTON ||
906      this.operationType === SubHeaderV2OperationType.TEXT_ARROW) {
907      Button({ type: ButtonType.Normal, stateEffect: false }) {
908        this.rightArea();
909      }
910      .focusable(this.operationItems ? true : false)
911      .align(Alignment.Start)
912      .rightAreaClickEvent()
913      .rightAreaParentAgingStyles()
914      .backgroundColor($r('sys.color.ohos_id_color_sub_background_transparent'))
915      .hoverEffect(HoverEffect.None)
916      .accessibilityGroup(true)
917      .accessibilityText(this.getRightAreaAccessibilityText())
918      .accessibilityDescription(this.getAccessibilityDescription())
919    } else {
920      Row() {
921        this.rightArea();
922      }
923      .focusable(this.operationItems && this.operationType !== SubHeaderV2OperationType.LOADING ? true : false)
924      .justifyContent(FlexAlign.Start)
925      .rightAreaClickEvent()
926      .rightAreaParentAgingStyles()
927    }
928  }
929
930  @Styles
931  private rightAreaParentAgingStyles() {
932    .margin({
933      bottom: getResourceValue('sys.float.padding_level4'),
934    })
935    .padding({
936      // 'sys.float.margin_left' id,value: 16vp
937      start: LengthMetrics.vp(getResourceValue('sys.float.margin_left') - PADDING_LEFT),
938      // 'sys.float.margin_right' id,value: 16vp
939      end: LengthMetrics.vp(getResourceValue('sys.float.margin_right')),
940    })
941    .accessibilityLevel(this.operationType === SubHeaderV2OperationType.BUTTON ||
942      this.operationType === SubHeaderV2OperationType.TEXT_ARROW ? this.getRightAreaAccessibilityLevel() : 'no')
943  }
944
945  private getRightAreaAccessibilityText(): string | undefined {
946    if (!this.operationItems || this.operationItems?.length <= 0) {
947      return '';
948    }
949    if (this.operationItems[0]?.accessibilityText && this.operationItems[0]?.accessibilityText !== '') {
950      return this.operationItems[0].accessibilityText as string;
951    } else {
952      if (this.operationType === SubHeaderV2OperationType.TEXT_ARROW &&
953        this.operationItems[0]?.content.toString().length <= 0) {
954        // 播报:更多、more等, 使用的字段是:sys.string.ohos_toolbar_more
955        return Util.getStringByResource(125833704, '');
956      }
957    }
958    return '';
959  }
960
961  private getMinHeight(): Length {
962    if (this.title?.secondaryTitle && this.icon) {
963      return getResourceValue('sys.float.subheader_single_subtitle_height');
964    } else if (this.title?.secondaryTitle && this.title?.primaryTitle) {
965      return getResourceValue('sys.float.subheader_double_height');
966    } else if (this.title?.primaryTitle || this.select) {
967      return getResourceValue('sys.float.subheader_single_title_height');
968    }
969    return getResourceValue('sys.float.subheader_single_subtitle_height');
970  }
971
972  private getAreaPadding(): LocalizedPadding {
973    let padding: LocalizedPadding = {};
974    if (!this.titleBuilder && ((this.title?.secondaryTitle && this.icon) ||
975      (!this.title?.primaryTitle && this.title?.secondaryTitle))) {
976      padding = {
977        start: LengthMetrics.vp(getResourceValue('sys.float.padding_level6')),
978        end: LengthMetrics.vp(getResourceValue('sys.float.padding_level6')),
979      }
980    } else if (this.select) {
981      padding = {
982        top: LengthMetrics.vp(getResourceValue('sys.float.padding_level2')),
983        bottom: LengthMetrics.vp(getResourceValue('sys.float.padding_level2')),
984      }
985    }
986    return padding;
987  }
988
989  build() {
990    if (this.isSuitableAging()) {
991      Column() {
992        Row() {
993          this.leftArea();
994        }
995        .margin({
996          top: LengthMetrics.vp(getResourceValue('sys.float.padding_level8')),
997          bottom: LengthMetrics.vp(getResourceValue('sys.float.padding_level1')),
998        })
999        .padding({
1000          start: LengthMetrics.vp(getResourceValue('sys.float.margin_left')),
1001          end: LengthMetrics.vp(getResourceValue('sys.float.margin_right')),
1002        })
1003        .width('100%')
1004        .accessibilityGroup(this.isLeftAreaAccessibilityGroup())
1005        .accessibilityDescription(this.select ? '' : Util.getStringByResource(125834353, ''))
1006
1007        if (this.isRightAreaExists()) {
1008          this.rightAreaParentAging();
1009        }
1010      }
1011      .constraintSize({ minHeight: this.getMinHeight() })
1012      .padding(this.getAreaPadding())
1013      .alignItems(HorizontalAlign.Start)
1014    } else {
1015      Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.End }) {
1016        Row() {
1017          this.leftArea();
1018        }
1019        .margin({
1020          top: this.fontSize >= MIN_FONT_SIZE ? getResourceValue('sys.float.padding_level8') : '',
1021          bottom: this.fontSize >= MIN_FONT_SIZE ? getResourceValue('sys.float.padding_level4') : '',
1022        })
1023        .width('100%')
1024        .flexShrink(1)
1025        .accessibilityGroup(this.isLeftAreaAccessibilityGroup())
1026        .accessibilityDescription(this.select ? '' : Util.getStringByResource(125834353, ''))
1027
1028        if (this.isRightAreaExists()) {
1029          this.rightAreaParent();
1030        }
1031      }
1032      .constraintSize({ minHeight: this.getMinHeight() })
1033      .margin(this.subHeaderMargin)
1034      .padding(this.getAreaPadding())
1035    }
1036  }
1037
1038  private getRightAreaAccessibilityLevel(): string {
1039    if (this.operationItems![0].accessibilityLevel && this.operationItems![0].accessibilityLevel !== '') {
1040      return this.operationItems![0].accessibilityLevel;
1041    }
1042    return 'yes';
1043  }
1044
1045  @Builder
1046  LoadingProcessStyle(): void {
1047    Row() {
1048      LoadingProgress()
1049        .width(OPERATE_ITEM_LENGTH)
1050        .height(OPERATE_ITEM_LENGTH)
1051        .color($r('sys.color.icon_secondary'))
1052    }
1053    .justifyContent(FlexAlign.End)
1054    .padding({
1055      top: getResourceValue('sys.float.padding_level2'),
1056      bottom: getResourceValue('sys.float.padding_level2'),
1057    })
1058    .margin({
1059      start: LengthMetrics.vp(getResourceValue('sys.float.padding_level4')),
1060    })
1061  }
1062
1063  @Builder
1064  dummyFunction(): void {
1065    Row() {
1066    }
1067  }
1068}
1069
1070@ComponentV2
1071struct SymbolGlyphChild {
1072  @Param @Require icon: SubHeaderV2IconType;
1073  @Param @Require fontColor: ResourceColor[];
1074  @Event changeSymbolWidth: (result: SizeResult) => void = () => {
1075  };
1076  result: SizeResult = {
1077    width: 0,
1078    height: 0
1079  };
1080
1081  onMeasureSize(selfLayoutInfo: GeometryInfo, children: Array<Measurable>, constraint: ConstraintSizeOptions) {
1082    children.forEach((child) => {
1083      this.result = child.measure(constraint);
1084    })
1085    console.log(`zzzz Child onMeasureSize ${JSON.stringify(this.result)}`)
1086    this.changeSymbolWidth(this.result)
1087    return this.result;
1088  }
1089
1090  build() {
1091    Column() {
1092      SymbolGlyph(this.icon as Resource)
1093        .fontSize(LEFT_ICON_SIZE)
1094        .fontColor(this.fontColor)
1095        .attributeModifier(this.icon as SymbolGlyphModifier)
1096        .margin({ end: LengthMetrics.vp(getResourceValue('sys.float.padding_level4')) })
1097        .flexShrink(0)
1098    }
1099  }
1100}
1101
1102@ComponentV2
1103struct SingleIconStyle {
1104  @Local bgColor: Resource = $r('sys.color.ohos_id_color_sub_background_transparent');
1105  @Local isFocus: boolean = false;
1106  @Param item: ContentIconOption | null = null;
1107  @Consumer('subHeaderV2Theme') subHeaderTheme: SubHeaderTheme = new SubHeaderTheme();
1108  @Param isSingleIcon: boolean = true;
1109
1110  private getRightIconAccessibilityText(): string | undefined {
1111    if (this.item?.accessibilityText) {
1112      return this.item.accessibilityText as string;
1113    }
1114    return '';
1115  }
1116
1117  private getRightIconAccessibilityLevel(): string {
1118    if (this.item?.accessibilityLevel && this.item?.accessibilityLevel !== '') {
1119      return this.item.accessibilityLevel;
1120    }
1121    return 'auto';
1122  }
1123
1124  private getRightIconAccessibilityDescription(): string | undefined {
1125    if (this.item?.accessibilityDescription && this.item?.accessibilityDescription !== '') {
1126      return this.item?.accessibilityDescription as string;
1127    }
1128    return '';
1129  }
1130
1131  build() {
1132    if (this.item && this.item.iconOptions) {
1133      Button({ type: ButtonType.Normal, stateEffect: false }) {
1134        this.IconZone();
1135      }
1136      .focusable(true)
1137      .defaultFocus(this.item.defaultFocus)
1138      .width(SINGLE_ICON_ZONE_SIZE)
1139      .height(SINGLE_ICON_ZONE_SIZE)
1140      .align(Alignment.Center)
1141      .backgroundColor(this.bgColor)
1142      .borderRadius(getResourceValue('sys.float.corner_radius_level4'))
1143      .accessibilityLevel(this.getRightIconAccessibilityLevel())
1144      .accessibilityText(this.getRightIconAccessibilityText())
1145      .accessibilityDescription(this.getRightIconAccessibilityDescription())
1146      .focusBox({
1147        margin: { value: INDEX_ZERO, unit: LengthUnit.VP },
1148        strokeColor: ColorMetrics.resourceColor(this.subHeaderTheme.borderFocusColor),
1149        strokeWidth: LengthMetrics.vp(getResourceValue('sys.float.outline_extra_larger')),
1150      })
1151      .stateStyles({
1152        pressed: pressedStyle,
1153        disabled: disabledStyle,
1154      })
1155      .onTouch((event) => {
1156        if (event.type === TouchType.Down || TouchType.Cancel) {
1157          this.bgColor = $r('sys.color.interactive_pressed');
1158        }
1159        if (event.type === TouchType.Up) {
1160          this.bgColor = $r('sys.color.ohos_id_color_sub_background_transparent');
1161        }
1162      })
1163      .onHover((isHover: boolean) => {
1164        if (isHover) {
1165          this.bgColor = $r('sys.color.interactive_hover');
1166        } else {
1167          this.bgColor = $r('sys.color.ohos_id_color_sub_background_transparent');
1168        }
1169      })
1170      .responseRegion(this.iconResponseRegion())
1171      .onClick((event) => {
1172        if (this.item?.action) {
1173          this.item?.action();
1174        }
1175      })
1176    }
1177  }
1178
1179  private iconResponseRegion(): Rectangle {
1180    if (this.isSingleIcon) {
1181      return {
1182        x: SINGLE_ICON_REGION_X,
1183        y: ICON_REGION_Y,
1184        width: MIN_HOT_AREA_LENGTH,
1185        height: MIN_HOT_AREA_LENGTH,
1186      };
1187    }
1188    return {
1189      x: ICON_REGION_X,
1190      y: ICON_REGION_Y,
1191      width: MULTI_ICON_REGION_WIDTH,
1192      height: MIN_HOT_AREA_LENGTH,
1193    };
1194  }
1195
1196  @Builder
1197  IconZone(): void {
1198    if (this.item && this.item.iconOptions) {
1199      if (Util.isSymbolResource(this.item.iconOptions)) {
1200        SymbolGlyph(this.item.iconOptions as Resource)
1201          .fontSize(RIGHT_SINGLE_ICON_SIZE)
1202          .fontColor([this.subHeaderTheme.rightIconColor])
1203          .attributeModifier(this.item.iconOptions as SymbolGlyphModifier)
1204          .focusable(true)
1205      } else {
1206        Image(this.item?.iconOptions as ResourceStr)
1207          .fillColor(this.subHeaderTheme.rightIconColor)
1208          .width(RIGHT_SINGLE_ICON_SIZE)
1209          .height(RIGHT_SINGLE_ICON_SIZE)
1210          .focusable(true)
1211          .draggable(false)
1212      }
1213    }
1214  }
1215}
1216
1217class Util {
1218  /**
1219   * 是否symbol资源
1220   * @param resourceStr  资源
1221   * @returns true:symbol资源;false:非symbol资源
1222   */
1223  public static isSymbolResource(resourceStr: SubHeaderV2IconType | undefined): boolean {
1224    if (!Util.isResourceType(resourceStr)) {
1225      return false;
1226    }
1227    if (resourceStr instanceof SymbolGlyphModifier) {
1228      return resourceStr instanceof SymbolGlyphModifier;
1229    }
1230    let resource = resourceStr as Resource;
1231    return resource.type === RESOURCE_TYPE_SYMBOL;
1232  }
1233
1234  /**
1235   * 是否Resource类型
1236   * @param resource 资源
1237   * @returns true:Resource类型;false:非Resource类型
1238   */
1239  public static isResourceType(resource: SubHeaderV2IconType | undefined): boolean {
1240    if (!resource) {
1241      return false;
1242    }
1243    if (typeof resource === 'string' || typeof resource === 'undefined') {
1244      return false;
1245    }
1246    return true;
1247  }
1248
1249  /**
1250   * get resource size
1251   *
1252   * @param resourceName resource id
1253   * @returns resource size
1254   */
1255  public static getNumberByResource(resourceId: number, defaultNumber: number): number {
1256    try {
1257      let resourceNumber: number = resourceManager.getSystemResourceManager().getNumber(resourceId);
1258      if (resourceNumber === 0) {
1259        return defaultNumber;
1260      } else {
1261        return resourceNumber;
1262      }
1263    } catch (error) {
1264      let code: number = (error as BusinessError).code;
1265      let message: string = (error as BusinessError).message;
1266      hilog.error(0x3900, 'Ace', `SubHeader getNumberByResource error, code: ${code}, message: ${message}`);
1267      return 0;
1268    }
1269  }
1270
1271  /**
1272   * get resource string
1273   *
1274   * @param resourceId resource id
1275   * @param defaultString default value
1276   * @returns resource string
1277   */
1278  public static getStringByResource(resourceId: number, defaultString: string): string {
1279    try {
1280      let resourceString: string = getContext().resourceManager.getStringSync(resourceId);
1281      if (resourceString === '') {
1282        return defaultString;
1283      } else {
1284        return resourceString;
1285      }
1286    } catch (error) {
1287      let code: number = (error as BusinessError).code;
1288      let message: string = (error as BusinessError).message;
1289      hilog.error(0x3900, 'Ace', `SubHeader getStringByResource error, code: ${code}, message: ${message}`);
1290      return '';
1291    }
1292  }
1293
1294  public static numberToSize(fontSize: Length): number {
1295    if (typeof fontSize === 'string') {
1296      const fontSizeNumber: number = parseInt(fontSize);
1297      return fontSizeNumber;
1298    } else if (typeof fontSize === 'number') {
1299      return fontSize;
1300    } else {
1301      return getContext().resourceManager.getNumber(fontSize);
1302    }
1303  }
1304
1305  public static symbolFontSize(fontSize: Length): Length {
1306    return Util.numberToSize(fontSize) + 'vp';
1307  }
1308}
1309
1310interface ResourceInfo {
1311  resourceId: number,
1312  defaultValue: number,
1313  resourceValue?: number,
1314}
1315
1316const RESOURCE_CACHE_MAP: HashMap<string, ResourceInfo> = new HashMap();
1317// padding_level0: 125830919, 0
1318RESOURCE_CACHE_MAP.set('sys.float.padding_level0', { resourceId: 125830919, defaultValue: 0 });
1319// padding_level1: 125830920, 2
1320RESOURCE_CACHE_MAP.set('sys.float.padding_level1', { resourceId: 125830920, defaultValue: 2 });
1321// padding_level2: 125830921, 4
1322RESOURCE_CACHE_MAP.set('sys.float.padding_level2', { resourceId: 125830921, defaultValue: 4 });
1323// padding_level3: 125830922, 6
1324RESOURCE_CACHE_MAP.set('sys.float.padding_level3', { resourceId: 125830922, defaultValue: 6 });
1325// padding_level4: 125830923, 8
1326RESOURCE_CACHE_MAP.set('sys.float.padding_level4', { resourceId: 125830923, defaultValue: 8 });
1327// padding_level6: 125830925, 12
1328RESOURCE_CACHE_MAP.set('sys.float.padding_level6', { resourceId: 125830925, defaultValue: 12 });
1329// padding_level8: 125830927, 16
1330RESOURCE_CACHE_MAP.set('sys.float.padding_level8', { resourceId: 125830927, defaultValue: 16 });
1331// margin_left: 125830936, 16
1332RESOURCE_CACHE_MAP.set('sys.float.margin_left', { resourceId: 125830936, defaultValue: 16 });
1333// margin_right: 125830937, 16
1334RESOURCE_CACHE_MAP.set('sys.float.margin_right', { resourceId: 125830937, defaultValue: 16 });
1335// outline_extra_larger: 125830951, 2
1336RESOURCE_CACHE_MAP.set('sys.float.outline_extra_larger', { resourceId: 125830951, defaultValue: 2 });
1337// corner_radius_level4: 125830909, 8
1338RESOURCE_CACHE_MAP.set('sys.float.corner_radius_level4', { resourceId: 125830909, defaultValue: 8 });
1339// Subtitle_S: 125830969, 14
1340RESOURCE_CACHE_MAP.set('sys.float.Subtitle_S', { resourceId: 125830969, defaultValue: 14 });
1341// subheader_title_font_size: 125834265, 18
1342RESOURCE_CACHE_MAP.set('sys.float.subheader_title_font_size', { resourceId: 125834265, defaultValue: 18 });
1343// Body_L: 125830970, 16
1344RESOURCE_CACHE_MAP.set('sys.float.Body_L', { resourceId: 125830970, defaultValue: 16 });
1345// interactive_disable: 125831067, 0.4
1346RESOURCE_CACHE_MAP.set('sys.float.interactive_disable', { resourceId: 125831067, defaultValue: 0.4 });
1347// subheader_single_title_height: 125834252 56
1348RESOURCE_CACHE_MAP.set('sys.float.subheader_single_title_height', { resourceId: 125834252, defaultValue: 56 });
1349// subheader_single_subtitle_height: 125834253 56
1350RESOURCE_CACHE_MAP.set('sys.float.subheader_single_subtitle_height', { resourceId: 125834253, defaultValue: 56 });
1351// subheader_double_height: 125834254 72
1352RESOURCE_CACHE_MAP.set('sys.float.subheader_double_height', { resourceId: 125834254, defaultValue: 72 });
1353// subheader_title_font_weight: 125834255 700
1354RESOURCE_CACHE_MAP.set('sys.float.subheader_title_font_weight', { resourceId: 125834255, defaultValue: 700 });
1355
1356function getResourceValue(resourceName: string): number {
1357  if (RESOURCE_CACHE_MAP.hasKey(resourceName)) {
1358    let resourceValue: number | undefined = RESOURCE_CACHE_MAP.get(resourceName).resourceValue;
1359    if (typeof resourceValue === 'number') {
1360      return resourceValue;
1361    } else {
1362      resourceValue = Util.getNumberByResource(RESOURCE_CACHE_MAP.get(resourceName).resourceId,
1363        RESOURCE_CACHE_MAP.get(resourceName).defaultValue);
1364      RESOURCE_CACHE_MAP.get(resourceName).resourceValue = resourceValue;
1365      return resourceValue;
1366    }
1367  }
1368  return 0;
1369}
1370
1371@Component
1372struct TextArrowLayout {
1373  @Builder
1374  doNothingBuilder(): void {
1375  };
1376
1377  @BuilderParam textArrowBuilder: () => void = this.doNothingBuilder;
1378
1379  onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Layoutable[],
1380    constraint: ConstraintSizeOptions) {
1381    let currentX: number = 0;
1382    let currentY: number = 0;
1383    for (let index = 0; index < children.length; index++) {
1384      let child = children[index];
1385      child.layout({ x: currentX, y: currentY });
1386    }
1387  }
1388
1389  onMeasureSize(selfLayoutInfo: GeometryInfo, children: Measurable[],
1390    constraint: ConstraintSizeOptions): SizeResult {
1391    let textArrowWidth: number = ARROW_ICON_WIDTH;
1392    let textArrowHeight: number = OPERATE_ITEM_LENGTH;
1393
1394    let textChild: Measurable = children[INDEX_ZERO];
1395    let textConstraint: ConstraintSizeOptions = {
1396      minWidth: Math.max(textArrowWidth, Number(constraint.minWidth)),
1397      maxWidth: constraint.maxWidth,
1398      minHeight: Math.max(textArrowHeight, Number(constraint.minHeight)),
1399      maxHeight: constraint.maxHeight,
1400    };
1401    let textMeasureResult: MeasureResult = textChild.measure(textConstraint);
1402    textArrowWidth = Math.max(textArrowWidth, textMeasureResult.width);
1403    textArrowHeight = Math.max(textArrowHeight, textMeasureResult.height);
1404
1405    let arrowChild: Measurable = children[INDEX_ONE];
1406    let arrowConstraint: ConstraintSizeOptions = {
1407      minWidth: textArrowWidth,
1408      maxWidth: textArrowWidth,
1409      minHeight: textArrowHeight,
1410      maxHeight: textArrowHeight,
1411    };
1412    arrowChild.measure(arrowConstraint);
1413    return { width: textArrowWidth, height: textArrowHeight };
1414  }
1415
1416  build() {
1417    this.textArrowBuilder();
1418  }
1419}