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