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 */
15import curves from '@ohos.curves';
16import { KeyCode } from '@ohos.multimodalInput.keyCode';
17import util from '@ohos.util';
18import { LengthMetrics } from '@ohos.arkui.node';
19import I18n from '@ohos.i18n';
20
21const MIN_ITEM_COUNT = 2
22const MAX_ITEM_COUNT = 5
23const DEFAULT_MAX_FONT_SCALE: number = 1
24const MAX_MAX_FONT_SCALE: number = 2
25const MIN_MAX_FONT_SCALE: number = 1
26
27interface SegmentButtonThemeInterface {
28  FONT_COLOR: ResourceColor,
29  TAB_SELECTED_FONT_COLOR: ResourceColor,
30  CAPSULE_SELECTED_FONT_COLOR: ResourceColor,
31  FONT_SIZE: DimensionNoPercentage,
32  SELECTED_FONT_SIZE: DimensionNoPercentage,
33  BACKGROUND_COLOR: ResourceColor,
34  TAB_SELECTED_BACKGROUND_COLOR: ResourceColor,
35  CAPSULE_SELECTED_BACKGROUND_COLOR: ResourceColor,
36  FOCUS_BORDER_COLOR: ResourceColor,
37  HOVER_COLOR: ResourceColor,
38  PRESS_COLOR: ResourceColor,
39  BACKGROUND_BLUR_STYLE: BlurStyle,
40}
41
42const segmentButtonTheme: SegmentButtonThemeInterface = {
43  FONT_COLOR: $r('sys.color.ohos_id_color_text_secondary'),
44  TAB_SELECTED_FONT_COLOR: $r('sys.color.ohos_id_color_text_primary'),
45  CAPSULE_SELECTED_FONT_COLOR: $r('sys.color.ohos_id_color_foreground_contrary'),
46  FONT_SIZE: $r('sys.float.ohos_id_text_size_body2'),
47  SELECTED_FONT_SIZE: $r('sys.float.ohos_id_text_size_body2'),
48  BACKGROUND_COLOR: $r('sys.color.ohos_id_color_button_normal'),
49  TAB_SELECTED_BACKGROUND_COLOR: $r('sys.color.ohos_id_color_foreground_contrary'),
50  CAPSULE_SELECTED_BACKGROUND_COLOR: $r('sys.color.ohos_id_color_emphasize'),
51  FOCUS_BORDER_COLOR: $r('sys.color.ohos_id_color_focused_outline'),
52  HOVER_COLOR: $r('sys.color.ohos_id_color_hover'),
53  PRESS_COLOR: $r('sys.color.ohos_id_color_click_effect'),
54  BACKGROUND_BLUR_STYLE: BlurStyle.NONE
55}
56
57interface Point {
58  x: number
59  y: number
60}
61
62function nearEqual(first: number, second: number): boolean {
63  return Math.abs(first - second) < 0.001
64}
65
66interface SegmentButtonTextItem {
67  text: ResourceStr
68  accessibilityLevel?: string
69  accessibilityDescription?: ResourceStr
70}
71
72interface SegmentButtonIconItem {
73  icon: ResourceStr,
74  iconAccessibilityText?: ResourceStr
75  selectedIcon: ResourceStr
76  selectedIconAccessibilityText?: ResourceStr
77  accessibilityLevel?: string
78  accessibilityDescription?: ResourceStr
79}
80
81interface SegmentButtonIconTextItem {
82  icon: ResourceStr,
83  iconAccessibilityText?: ResourceStr
84  selectedIcon: ResourceStr,
85  selectedIconAccessibilityText?: ResourceStr
86  text: ResourceStr
87  accessibilityLevel?: string
88  accessibilityDescription?: ResourceStr
89}
90
91type DimensionNoPercentage = PX | VP | FP | LPX | Resource
92
93interface CommonSegmentButtonOptions {
94  fontColor?: ResourceColor
95  selectedFontColor?: ResourceColor
96  fontSize?: DimensionNoPercentage
97  selectedFontSize?: DimensionNoPercentage
98  fontWeight?: FontWeight
99  selectedFontWeight?: FontWeight
100  backgroundColor?: ResourceColor
101  selectedBackgroundColor?: ResourceColor
102  imageSize?: SizeOptions
103  buttonPadding?: Padding | Dimension
104  textPadding?: Padding | Dimension
105  localizedTextPadding?: LocalizedPadding
106  localizedButtonPadding?: LocalizedPadding
107  backgroundBlurStyle?: BlurStyle
108  direction?: Direction
109}
110
111type ItemRestriction<T> = [T, T, T?, T?, T?]
112type SegmentButtonItemTuple = ItemRestriction<SegmentButtonTextItem> |
113ItemRestriction<SegmentButtonIconItem> | ItemRestriction<SegmentButtonIconTextItem>
114type SegmentButtonItemArray = Array<SegmentButtonTextItem> |
115Array<SegmentButtonIconItem> | Array<SegmentButtonIconTextItem>
116
117export interface TabSegmentButtonConstructionOptions extends CommonSegmentButtonOptions {
118  buttons: ItemRestriction<SegmentButtonTextItem>
119}
120
121export interface CapsuleSegmentButtonConstructionOptions extends CommonSegmentButtonOptions {
122  buttons: SegmentButtonItemTuple
123  multiply?: boolean
124}
125
126export interface TabSegmentButtonOptions extends TabSegmentButtonConstructionOptions {
127  type: 'tab',
128}
129
130export interface CapsuleSegmentButtonOptions extends CapsuleSegmentButtonConstructionOptions {
131  type: 'capsule'
132}
133
134interface SegmentButtonItemOptionsConstructorOptions {
135  icon?: ResourceStr
136  iconAccessibilityText?: ResourceStr
137  selectedIcon?: ResourceStr
138  selectedIconAccessibilityText?: ResourceStr
139  text?: ResourceStr
140  accessibilityLevel?: string
141  accessibilityDescription?: ResourceStr
142}
143
144@Observed
145class SegmentButtonItemOptions {
146  public icon?: ResourceStr
147  public iconAccessibilityText?: ResourceStr
148  public selectedIcon?: ResourceStr
149  public selectedIconAccessibilityText?: ResourceStr
150  public text?: ResourceStr
151  public accessibilityLevel?: string
152  public accessibilityDescription?: ResourceStr
153
154  constructor(options: SegmentButtonItemOptionsConstructorOptions) {
155    this.icon = options.icon
156    this.selectedIcon = options.selectedIcon
157    this.text = options.text
158    this.iconAccessibilityText = options.iconAccessibilityText
159    this.selectedIconAccessibilityText = options.selectedIconAccessibilityText
160    this.accessibilityLevel = options.accessibilityLevel
161    this.accessibilityDescription = options.accessibilityDescription
162  }
163}
164
165@Observed
166export class SegmentButtonItemOptionsArray extends Array<SegmentButtonItemOptions> {
167  public changeStartIndex: number | undefined = void 0
168  public deleteCount: number | undefined = void 0
169  public addLength: number | undefined = void 0
170
171  constructor(length: number)
172
173  constructor(elements: SegmentButtonItemTuple)
174
175  constructor(elementsOrLength: SegmentButtonItemTuple | number) {
176
177    super(typeof elementsOrLength === 'number' ? elementsOrLength : 0);
178
179    if (typeof elementsOrLength !== 'number' && elementsOrLength !== void 0) {
180      super.push(...elementsOrLength.map((element?: SegmentButtonTextItem | SegmentButtonIconItem |
181      SegmentButtonIconTextItem) => new SegmentButtonItemOptions(element as
182      SegmentButtonItemOptionsConstructorOptions)))
183    }
184  }
185
186  push(...items: SegmentButtonItemArray): number {
187    if (this.length + items.length > MAX_ITEM_COUNT) {
188      console.warn('Exceeded the maximum number of elements (5).')
189      return this.length
190    }
191    this.changeStartIndex = this.length
192    this.deleteCount = 0
193    this.addLength = items.length
194    return super.push(...items.map((element: SegmentButtonItemOptionsConstructorOptions) =>
195    new SegmentButtonItemOptions(element)))
196  }
197
198  pop() {
199    if (this.length <= MIN_ITEM_COUNT) {
200      console.warn('Below the minimum number of elements (2).')
201      return void 0
202    }
203    this.changeStartIndex = this.length - 1
204    this.deleteCount = 1
205    this.addLength = 0
206    return super.pop()
207  }
208
209  shift() {
210    if (this.length <= MIN_ITEM_COUNT) {
211      console.warn('Below the minimum number of elements (2).')
212      return void 0
213    }
214    this.changeStartIndex = 0
215    this.deleteCount = 1
216    this.addLength = 0
217    return super.shift()
218  }
219
220  unshift(...items: SegmentButtonItemArray): number {
221    if (this.length + items.length > MAX_ITEM_COUNT) {
222      console.warn('Exceeded the maximum number of elements (5).')
223      return this.length
224    }
225    if (items.length > 0) {
226      this.changeStartIndex = 0
227      this.deleteCount = 0
228      this.addLength = items.length
229    }
230    return super.unshift(...items.map((element: SegmentButtonItemOptionsConstructorOptions) =>
231    new SegmentButtonItemOptions(element)))
232  }
233
234  splice(start: number, deleteCount: number, ...items: SegmentButtonItemOptions[]): SegmentButtonItemOptions[] {
235    let length = (this.length - deleteCount) < 0 ? 0 : (this.length - deleteCount)
236    length += items.length
237    if (length < MIN_ITEM_COUNT) {
238      console.warn('Below the minimum number of elements (2).')
239      return []
240    }
241    if (length > MAX_ITEM_COUNT) {
242      console.warn('Exceeded the maximum number of elements (5).')
243      return []
244    }
245    this.changeStartIndex = start
246    this.deleteCount = deleteCount
247    this.addLength = items.length
248    return super.splice(start, deleteCount, ...items)
249  }
250
251  static create(elements: SegmentButtonItemTuple): SegmentButtonItemOptionsArray {
252    return new SegmentButtonItemOptionsArray(elements)
253  }
254}
255
256@Observed
257export class SegmentButtonOptions {
258  public type: 'tab' | 'capsule'
259  public multiply: boolean = false
260  public fontColor: ResourceColor
261  public selectedFontColor: ResourceColor
262  public fontSize: DimensionNoPercentage
263  public selectedFontSize: DimensionNoPercentage
264  public fontWeight: FontWeight
265  public selectedFontWeight: FontWeight
266  public backgroundColor: ResourceColor
267  public selectedBackgroundColor: ResourceColor
268  public imageSize: SizeOptions
269  public buttonPadding: Padding | Dimension | undefined
270  public textPadding: Padding | Dimension | undefined
271  public componentPadding: Padding | Dimension
272  public localizedTextPadding?: LocalizedPadding
273  public localizedButtonPadding?: LocalizedPadding
274  public showText: boolean = false
275  public showIcon: boolean = false
276  public iconTextRadius?: number
277  public iconTextBackgroundRadius?: number
278  public backgroundBlurStyle: BlurStyle
279  public direction?: Direction
280  private _buttons: SegmentButtonItemOptionsArray | undefined = void 0
281
282  get buttons() {
283    return this._buttons
284  }
285
286  set buttons(val) {
287    if (this._buttons !== void 0 && this._buttons !== val) {
288      this.onButtonsChange?.()
289    }
290    this._buttons = val
291  }
292
293  public onButtonsChange?: () => void
294
295  constructor(options: TabSegmentButtonOptions | CapsuleSegmentButtonOptions) {
296    this.fontColor = options.fontColor ?? segmentButtonTheme.FONT_COLOR
297    this.selectedFontColor = options.selectedFontColor ?? segmentButtonTheme.TAB_SELECTED_FONT_COLOR
298    this.fontSize = options.fontSize ?? segmentButtonTheme.FONT_SIZE
299    this.selectedFontSize = options.selectedFontSize ?? segmentButtonTheme.SELECTED_FONT_SIZE
300    this.fontWeight = options.fontWeight ?? FontWeight.Regular
301    this.selectedFontWeight = options.selectedFontWeight ?? FontWeight.Medium
302    this.backgroundColor = options.backgroundColor ?? segmentButtonTheme.BACKGROUND_COLOR
303    this.selectedBackgroundColor = options.selectedBackgroundColor ?? segmentButtonTheme.TAB_SELECTED_BACKGROUND_COLOR
304    this.imageSize = options.imageSize ?? { width: 24, height: 24 }
305    this.buttonPadding = options.buttonPadding
306    this.textPadding = options.textPadding
307    this.type = options.type
308    this.backgroundBlurStyle = options.backgroundBlurStyle ?? segmentButtonTheme.BACKGROUND_BLUR_STYLE
309    this.localizedTextPadding = options.localizedTextPadding
310    this.localizedButtonPadding = options.localizedButtonPadding
311    this.direction = options.direction ?? Direction.Auto
312    this.buttons = new SegmentButtonItemOptionsArray(options.buttons)
313    if (this.type === 'capsule') {
314      this.multiply = (options as CapsuleSegmentButtonOptions).multiply ?? false
315      this.onButtonsUpdated();
316      this.selectedFontColor = options.selectedFontColor ?? segmentButtonTheme.CAPSULE_SELECTED_FONT_COLOR
317      this.selectedBackgroundColor = options.selectedBackgroundColor ??
318      segmentButtonTheme.CAPSULE_SELECTED_BACKGROUND_COLOR
319    } else {
320      this.showText = true
321    }
322    this.componentPadding = this.multiply ? 0 : 2
323  }
324
325  public onButtonsUpdated() {
326    this.buttons?.forEach(button => {
327        this.showText ||= button.text !== void 0;
328        this.showIcon ||= button.icon !== void 0 || button.selectedIcon !== void 0;
329      })
330      if (this.showText && this.showIcon) {
331        this.iconTextRadius = 12;
332        this.iconTextBackgroundRadius = 14;
333      }
334  }
335
336  static tab(options: TabSegmentButtonConstructionOptions): SegmentButtonOptions {
337    return new SegmentButtonOptions({
338      type: 'tab',
339      buttons: options.buttons,
340      fontColor: options.fontColor,
341      selectedFontColor: options.selectedFontColor,
342      fontSize: options.fontSize,
343      selectedFontSize: options.selectedFontSize,
344      fontWeight: options.fontWeight,
345      selectedFontWeight: options.selectedFontWeight,
346      backgroundColor: options.backgroundColor,
347      selectedBackgroundColor: options.selectedBackgroundColor,
348      imageSize: options.imageSize,
349      buttonPadding: options.buttonPadding,
350      textPadding: options.textPadding,
351      localizedTextPadding: options.localizedTextPadding,
352      localizedButtonPadding: options.localizedButtonPadding,
353      backgroundBlurStyle: options.backgroundBlurStyle,
354      direction: options.direction
355    })
356  }
357
358  static capsule(options: CapsuleSegmentButtonConstructionOptions): SegmentButtonOptions {
359    return new SegmentButtonOptions({
360      type: 'capsule',
361      buttons: options.buttons,
362      multiply: options.multiply,
363      fontColor: options.fontColor,
364      selectedFontColor: options.selectedFontColor,
365      fontSize: options.fontSize,
366      selectedFontSize: options.selectedFontSize,
367      fontWeight: options.fontWeight,
368      selectedFontWeight: options.selectedFontWeight,
369      backgroundColor: options.backgroundColor,
370      selectedBackgroundColor: options.selectedBackgroundColor,
371      imageSize: options.imageSize,
372      buttonPadding: options.buttonPadding,
373      textPadding: options.textPadding,
374      localizedTextPadding: options.localizedTextPadding,
375      localizedButtonPadding: options.localizedButtonPadding,
376      backgroundBlurStyle: options.backgroundBlurStyle,
377      direction: options.direction
378    })
379  }
380}
381
382@Component
383struct MultiSelectBackground {
384  @ObjectLink optionsArray: SegmentButtonItemOptionsArray
385  @ObjectLink options: SegmentButtonOptions
386  @Consume buttonBorderRadius: LocalizedBorderRadiuses[]
387  @Consume buttonItemsSize: SizeOptions[]
388
389  build() {
390    Row({ space: 1 }) {
391      ForEach(this.optionsArray, (_: SegmentButtonItemOptions, index) => {
392        if (index < MAX_ITEM_COUNT) {
393          Stack()
394            .direction(this.options.direction)
395            .layoutWeight(1)
396            .height(this.buttonItemsSize[index].height)
397            .backgroundColor(this.options.backgroundColor ?? segmentButtonTheme.BACKGROUND_COLOR)
398            .borderRadius(this.buttonBorderRadius[index])
399            .backgroundBlurStyle(this.options.backgroundBlurStyle)
400        }
401      })
402    }
403    .direction(this.options.direction)
404    .padding(this.options.componentPadding)
405  }
406}
407
408@Component
409struct SelectItem {
410  @ObjectLink optionsArray: SegmentButtonItemOptionsArray
411  @ObjectLink options: SegmentButtonOptions
412  @Link selectedIndexes: number[]
413  @Consume buttonItemsSize: SizeOptions[]
414  @Consume selectedItemPosition: LocalizedEdges
415  @Consume zoomScaleArray: number[]
416  @Consume buttonBorderRadius: LocalizedBorderRadiuses[]
417
418  build() {
419    if (this.selectedIndexes !== void 0 && this.selectedIndexes.length !== 0) {
420      Stack()
421        .direction(this.options.direction)
422        .borderRadius(this.buttonBorderRadius[this.selectedIndexes[0]])
423        .size(this.buttonItemsSize[this.selectedIndexes[0]])
424        .backgroundColor(this.options.selectedBackgroundColor ??
425          (this.options.type === 'tab' ? segmentButtonTheme.TAB_SELECTED_BACKGROUND_COLOR :
426          segmentButtonTheme.CAPSULE_SELECTED_BACKGROUND_COLOR))
427        .position(this.selectedItemPosition)
428        .scale({ x: this.zoomScaleArray[this.selectedIndexes[0]], y: this.zoomScaleArray[this.selectedIndexes[0]] })
429        .shadow(ShadowStyle.OUTER_DEFAULT_XS)
430    }
431  }
432}
433
434@Component
435struct MultiSelectItemArray {
436  @ObjectLink optionsArray: SegmentButtonItemOptionsArray
437  @ObjectLink @Watch('onOptionsChange') options: SegmentButtonOptions
438  @Link @Watch('onSelectedChange') selectedIndexes: number[]
439  @Consume buttonItemsSize: SizeOptions[]
440  @Consume zoomScaleArray: number[]
441  @Consume buttonBorderRadius: LocalizedBorderRadiuses[]
442  @State multiColor: ResourceColor[] = Array.from({ length: MAX_ITEM_COUNT }, (_: Object, index) => Color.Transparent)
443
444  onOptionsChange() {
445    for (let i = 0; i < this.selectedIndexes.length; i++) {
446      this.multiColor[this.selectedIndexes[i]] = this.options.selectedBackgroundColor ??
447      segmentButtonTheme.CAPSULE_SELECTED_BACKGROUND_COLOR
448    }
449  }
450
451  onSelectedChange() {
452    for (let i = 0; i < MAX_ITEM_COUNT; i++) {
453      this.multiColor[i] = Color.Transparent
454    }
455    for (let i = 0; i < this.selectedIndexes.length; i++) {
456      this.multiColor[this.selectedIndexes[i]] = this.options.selectedBackgroundColor ??
457      segmentButtonTheme.CAPSULE_SELECTED_BACKGROUND_COLOR
458    }
459  }
460
461  aboutToAppear() {
462    for (let i = 0; i < this.selectedIndexes.length; i++) {
463      this.multiColor[this.selectedIndexes[i]] = this.options.selectedBackgroundColor ??
464      segmentButtonTheme.CAPSULE_SELECTED_BACKGROUND_COLOR
465    }
466  }
467
468  build() {
469    Row({ space: 1 }) {
470      ForEach(this.optionsArray, (_: SegmentButtonItemOptions, index) => {
471        if (index < MAX_ITEM_COUNT) {
472          Stack()
473            .direction(this.options.direction)
474            .width(this.buttonItemsSize[index].width)
475            .height(this.buttonItemsSize[index].height)
476            .backgroundColor(this.multiColor[index])
477            .borderRadius(this.buttonBorderRadius[index])
478        }
479      })
480    }
481    .direction(this.options.direction)
482    .padding(this.options.componentPadding)
483  }
484}
485
486@Component
487struct SegmentButtonItem {
488  @Link selectedIndexes: number[]
489  @Link focusIndex: number
490  @Prop @Require maxFontScale: number | Resource
491  @ObjectLink itemOptions: SegmentButtonItemOptions
492  @ObjectLink options: SegmentButtonOptions;
493  @ObjectLink property: ItemProperty
494  @Prop index: number
495  private groupId: string = ''
496
497  private getTextPadding(): Padding | Dimension | LocalizedPadding {
498    if (this.options.localizedTextPadding) {
499      return this.options.localizedTextPadding
500    }
501    if (this.options.textPadding !== void (0)) {
502      return this.options.textPadding
503    }
504    return 0
505  }
506
507  private getButtonPadding(): Padding | Dimension | LocalizedPadding {
508    if (this.options.localizedButtonPadding) {
509      return this.options.localizedButtonPadding
510    }
511    if (this.options.buttonPadding !== void (0)) {
512      return this.options.buttonPadding
513    }
514    if (this.options.type === 'capsule' && this.options.showText && this.options.showIcon) {
515      return {
516        top: LengthMetrics.vp(6),
517        bottom: LengthMetrics.vp(6),
518        start: LengthMetrics.vp(8),
519        end: LengthMetrics.vp(8)
520      }
521    }
522    return {
523      top: LengthMetrics.vp(4),
524      bottom: LengthMetrics.vp(4),
525      start: LengthMetrics.vp(8),
526      end: LengthMetrics.vp(8)
527    }
528  }
529
530  private getAccessibilityText(): string {
531    try {
532      if (this.selectedIndexes.includes(this.index) && this.itemOptions.selectedIconAccessibilityText) {
533        return (typeof this.itemOptions.selectedIconAccessibilityText === 'string') ?
534        this.itemOptions.selectedIconAccessibilityText :
535        getContext(this).resourceManager.getStringSync((this.itemOptions.selectedIconAccessibilityText as Resource).id)
536      } else if (this.itemOptions.iconAccessibilityText) {
537        return (typeof this.itemOptions.iconAccessibilityText === 'string') ?
538        this.itemOptions.iconAccessibilityText :
539        getContext(this).resourceManager.getStringSync((this.itemOptions.iconAccessibilityText as Resource).id)
540      }
541    } catch (error) {
542      console.error(`Ace SegmentButton getAccessibilityText, error: ${error.toString()}`);
543    }
544    return '';
545  }
546
547  build() {
548    Column({ space: 2 }) {
549      if (this.options.showIcon) {
550        Image(this.property.isSelected ? this.itemOptions.selectedIcon : this.itemOptions.icon)
551          .direction(this.options.direction)
552          .size(this.options.imageSize ?? { width: 24, height: 24 })
553          .focusable(!this.options.showText)
554          .draggable(false)
555          .fillColor(this.property.isSelected ? (this.options.selectedFontColor ??
556          segmentButtonTheme.CAPSULE_SELECTED_FONT_COLOR) : (this.options.fontColor ??
557          segmentButtonTheme.FONT_COLOR))
558          .accessibilityText(this.getAccessibilityText())
559      }
560      if (this.options.showText) {
561        Text(this.itemOptions.text)
562          .direction(this.options.direction)
563          .fontColor(this.property.fontColor)
564          .fontWeight(this.property.fontWeight)
565          .fontSize(this.property.fontSize)
566          .minFontSize(9)
567          .maxFontSize(this.property.fontSize)
568          .maxFontScale(this.maxFontScale)
569          .textOverflow({ overflow: TextOverflow.Ellipsis })
570          .maxLines(1)
571          .textAlign(TextAlign.Center)
572          .focusable(true)
573          .padding(this.getTextPadding())
574      }
575    }
576    .direction(this.options.direction)
577    .focusScopePriority(this.groupId, Math.min(...this.selectedIndexes) === this.index ? FocusPriority.PREVIOUS : FocusPriority.AUTO)
578    .justifyContent(FlexAlign.Center)
579    .padding(this.getButtonPadding())
580    .constraintSize({ minHeight: 28 })
581  }
582}
583
584@Observed
585class HoverColorProperty {
586  public hoverColor: ResourceColor = Color.Transparent
587}
588
589@Component
590struct PressAndHoverEffect {
591  @Consume buttonItemsSize: SizeOptions[]
592  @Prop press: boolean
593  @Prop hover: boolean
594  @ObjectLink colorProperty: HoverColorProperty
595  @Consume buttonBorderRadius: LocalizedBorderRadiuses[]
596  @ObjectLink options: SegmentButtonOptions;
597  pressIndex: number = 0
598  pressColor: ResourceColor = segmentButtonTheme.PRESS_COLOR
599
600  build() {
601    Stack()
602      .direction(this.options.direction)
603      .size(this.buttonItemsSize[this.pressIndex])
604      .backgroundColor(this.press && this.hover ? this.pressColor : this.colorProperty.hoverColor)
605      .borderRadius(this.buttonBorderRadius[this.pressIndex])
606  }
607}
608
609@Component
610struct SegmentButtonItemArrayComponent {
611  @ObjectLink @Watch('onOptionsArrayChange') optionsArray: SegmentButtonItemOptionsArray
612  @ObjectLink @Watch('onOptionsChange') options: SegmentButtonOptions
613  @Link selectedIndexes: number[]
614  @Consume componentSize: SizeOptions
615  @Consume buttonBorderRadius: LocalizedBorderRadiuses[]
616  @Consume @Watch('onButtonItemsSizeChange') buttonItemsSize: SizeOptions[]
617  @Consume buttonItemsPosition: LocalizedEdges[]
618  @Consume focusIndex: number
619  @Consume zoomScaleArray: number[]
620  @Consume buttonItemProperty: ItemProperty[]
621  @Consume buttonItemsSelected: boolean[]
622  @Link pressArray: boolean[]
623  @Link hoverArray: boolean[]
624  @Link hoverColorArray: HoverColorProperty[]
625  @Prop @Require maxFontScale: number | Resource
626  @State buttonWidth: number[] = Array.from({ length: MAX_ITEM_COUNT }, (_: Object, index) => 0)
627  @State buttonHeight: number[] = Array.from({ length: MAX_ITEM_COUNT }, (_: Object, index) => 0)
628  private buttonItemsRealHeight: number[] = Array.from({ length: MAX_ITEM_COUNT }, (_: Object, index) => 0)
629  private groupId: string = util.generateRandomUUID(true)
630
631  onButtonItemsSizeChange() {
632    this.buttonItemsSize.forEach((value, index) => {
633      this.buttonWidth[index] = value.width as number
634      this.buttonHeight[index] = value.height as number
635    })
636  }
637
638  changeSelectedIndexes(buttonsLength: number) {
639    if (this.optionsArray.changeStartIndex === void 0 || this.optionsArray.deleteCount === void 0 ||
640      this.optionsArray.addLength === void 0) {
641      return
642    }
643    if (!(this.options.multiply ?? false)) {
644      // Single-select
645      if (this.selectedIndexes[0] === void 0) {
646        return
647      }
648      if (this.selectedIndexes[0] < this.optionsArray.changeStartIndex) {
649        return
650      }
651      if (this.optionsArray.changeStartIndex + this.optionsArray.deleteCount > this.selectedIndexes[0]) {
652        if (this.options.type === 'tab') {
653          this.selectedIndexes[0] = 0
654        } else if (this.options.type === 'capsule') {
655          this.selectedIndexes = []
656        }
657      } else {
658        this.selectedIndexes[0] = this.selectedIndexes[0] - this.optionsArray.deleteCount + this.optionsArray.addLength
659      }
660    } else {
661      // Multi-select
662      let saveIndexes = this.selectedIndexes
663      for (let i = 0; i < this.optionsArray.deleteCount; i++) {
664        let deleteIndex = saveIndexes.indexOf(this.optionsArray.changeStartIndex)
665        let indexes = saveIndexes.map(value => this.optionsArray.changeStartIndex &&
666          (value > this.optionsArray.changeStartIndex) ? value - 1 : value)
667        if (deleteIndex !== -1) {
668          indexes.splice(deleteIndex, 1)
669        }
670        saveIndexes = indexes
671      }
672      for (let i = 0; i < this.optionsArray.addLength; i++) {
673        let indexes = saveIndexes.map(value => this.optionsArray.changeStartIndex &&
674          (value >= this.optionsArray.changeStartIndex) ? value + 1 : value)
675        saveIndexes = indexes
676      }
677      this.selectedIndexes = saveIndexes
678    }
679
680  }
681
682  changeFocusIndex(buttonsLength: number) {
683    if (this.optionsArray.changeStartIndex === void 0 || this.optionsArray.deleteCount === void 0 ||
684      this.optionsArray.addLength === void 0) {
685      return
686    }
687    if (this.focusIndex === -1) {
688      return
689    }
690    if (this.focusIndex < this.optionsArray.changeStartIndex) {
691      return
692    }
693    if (this.optionsArray.changeStartIndex + this.optionsArray.deleteCount > this.focusIndex) {
694      this.focusIndex = 0
695    } else {
696      this.focusIndex = this.focusIndex - this.optionsArray.deleteCount + this.optionsArray.addLength
697    }
698
699  }
700
701  onOptionsArrayChange() {
702    if (this.options === void 0 || this.options.buttons === void 0) {
703      return
704    }
705    let buttonsLength = Math.min(this.options.buttons.length, this.buttonItemsSize.length)
706    if (this.optionsArray.changeStartIndex !== void 0 && this.optionsArray.deleteCount !== void 0 &&
707      this.optionsArray.addLength !== void 0) {
708      this.changeSelectedIndexes(buttonsLength)
709      this.changeFocusIndex(buttonsLength)
710      this.optionsArray.changeStartIndex = void 0
711      this.optionsArray.deleteCount = void 0
712      this.optionsArray.addLength = void 0
713    }
714  }
715
716  onOptionsChange() {
717    if (this.options === void 0 || this.options.buttons === void 0) {
718      return
719    }
720    this.calculateBorderRadius()
721  }
722
723  aboutToAppear() {
724    for (let index = 0; index < this.buttonItemsRealHeight.length; index++) {
725      this.buttonItemsRealHeight[index] = 0
726    }
727  }
728
729  private getBorderRadius(index: number): LocalizedBorderRadiuses {
730    let borderRadius: LocalizedBorderRadiuses = this.buttonBorderRadius[index]
731    if (this.options.type === 'capsule' && this.buttonItemsSelected[this.focusIndex]) {
732      borderRadius.topStart = LengthMetrics.vp((borderRadius.topStart?.value ?? 0) + 4)
733      borderRadius.topEnd = LengthMetrics.vp((borderRadius.topEnd?.value ?? 0) + 4)
734      borderRadius.bottomStart = LengthMetrics.vp((borderRadius.bottomStart?.value ?? 0) + 4)
735      borderRadius.bottomEnd = LengthMetrics.vp((borderRadius.bottomEnd?.value ?? 0) + 4)
736    }
737    return borderRadius
738  }
739
740  @Builder
741  focusStack(index: number) {
742    Stack() {
743      Stack()
744        .direction(this.options.direction)
745        .borderRadius(this.getBorderRadius(index))
746        .size({
747          width: this.options.type === 'capsule' && this.buttonItemsSelected[this.focusIndex] ? this.buttonWidth[index] + 8 : this.buttonWidth[index],
748          height: this.options.type === 'capsule' && this.buttonItemsSelected[this.focusIndex] ? this.buttonHeight[index] + 8 : this.buttonHeight[index]
749        })
750        .borderColor(segmentButtonTheme.FOCUS_BORDER_COLOR)
751        .borderWidth(2)
752    }
753    .direction(this.options.direction)
754    .size({ width: 1, height: 1 })
755    .align(Alignment.Center)
756  }
757
758  calculateBorderRadius() {
759    let borderRadiusArray: LocalizedBorderRadiuses[] = Array.from({
760      length: MAX_ITEM_COUNT
761    }, (_: Object, index): LocalizedBorderRadiuses => {
762      return {
763        topStart: LengthMetrics.vp(0),
764        topEnd: LengthMetrics.vp(0),
765        bottomStart: LengthMetrics.vp(0),
766        bottomEnd: LengthMetrics.vp(0)
767      }
768    })
769    for (let index = 0; index < this.buttonBorderRadius.length; index++) {
770      let halfButtonItemsSizeHeight = this.buttonItemsSize[index].height as number / 2
771      if (this.options.type === 'tab' || !(this.options.multiply ?? false)) {
772        borderRadiusArray[index].topStart = LengthMetrics.vp(this.options.iconTextRadius ?? halfButtonItemsSizeHeight)
773        borderRadiusArray[index].topEnd = LengthMetrics.vp(this.options.iconTextRadius ?? halfButtonItemsSizeHeight)
774        borderRadiusArray[index].bottomStart = LengthMetrics.vp(this.options.iconTextRadius ?? halfButtonItemsSizeHeight)
775        borderRadiusArray[index].bottomEnd = LengthMetrics.vp(this.options.iconTextRadius ?? halfButtonItemsSizeHeight)
776      } else {
777        if (index === 0) {
778          borderRadiusArray[index].topStart = LengthMetrics.vp(this.options.iconTextRadius ?? halfButtonItemsSizeHeight)
779          borderRadiusArray[index].topEnd = LengthMetrics.vp(0)
780          borderRadiusArray[index].bottomStart = LengthMetrics.vp(this.options.iconTextRadius ?? halfButtonItemsSizeHeight)
781          borderRadiusArray[index].bottomEnd = LengthMetrics.vp(0)
782        } else if (this.options.buttons && index === Math.min(this.options.buttons.length,
783          this.buttonItemsSize.length) - 1) {
784          borderRadiusArray[index].topStart = LengthMetrics.vp(0)
785          borderRadiusArray[index].topEnd = LengthMetrics.vp(this.options.iconTextRadius ?? halfButtonItemsSizeHeight)
786          borderRadiusArray[index].bottomStart = LengthMetrics.vp(0)
787          borderRadiusArray[index].bottomEnd = LengthMetrics.vp(this.options.iconTextRadius ?? halfButtonItemsSizeHeight)
788        } else {
789          borderRadiusArray[index].topStart = LengthMetrics.vp(0)
790          borderRadiusArray[index].topEnd = LengthMetrics.vp(0)
791          borderRadiusArray[index].bottomStart = LengthMetrics.vp(0)
792          borderRadiusArray[index].bottomEnd = LengthMetrics.vp(0)
793        }
794      }
795    }
796    this.buttonBorderRadius = borderRadiusArray
797  }
798
799  getAccessibilityDescription(value?: ResourceStr): string {
800    if (value) {
801      try {
802        return (typeof value === 'string') ? value :
803        getContext(this).resourceManager.getStringSync((value as Resource).id)
804      } catch (error) {
805        console.error(`Ace SegmentButton getAccessibilityDescription, error: ${error.toString()}`);
806      }
807    }
808    return '';
809  }
810
811  build() {
812    if (this.optionsArray !== void 0 && this.optionsArray.length > 1) {
813      Row({ space: 1 }) {
814        ForEach(this.optionsArray, (item: SegmentButtonItemOptions, index) => {
815          if (index < MAX_ITEM_COUNT) {
816            Button() {
817              SegmentButtonItem({
818                selectedIndexes: $selectedIndexes,
819                focusIndex: this.focusIndex,
820                index: index,
821                itemOptions: item,
822                options: this.options,
823                property: this.buttonItemProperty[index],
824                groupId: this.groupId,
825                maxFontScale: this.maxFontScale
826              })
827                .onSizeChange((_, newValue) => {
828                  // Calculate height of items
829                  this.buttonItemsRealHeight[index] = newValue.height as number
830                  let maxHeight = Math.max(...this.buttonItemsRealHeight.slice(0, this.options.buttons ?
831                  this.options.buttons.length : 0))
832                  for (let index = 0; index < this.buttonItemsSize.length; index++) {
833                    this.buttonItemsSize[index] = { width: this.buttonItemsSize[index].width, height: maxHeight }
834                  }
835                  this.calculateBorderRadius()
836                })
837            }
838            .type(ButtonType.Normal)
839            .stateEffect(false)
840            .hoverEffect(HoverEffect.None)
841            .backgroundColor(Color.Transparent)
842            .accessibilityLevel(item.accessibilityLevel)
843            .accessibilitySelected(this.options.multiply ? undefined : this.selectedIndexes.includes(index))
844            .accessibilityChecked(this.options.multiply ? this.selectedIndexes.includes(index) : undefined)
845            .accessibilityDescription(this.getAccessibilityDescription(item.accessibilityDescription))
846            .direction(this.options.direction)
847            .borderRadius(this.buttonBorderRadius[index])
848            .scale({
849              x: this.options.type === 'capsule' && (this.options.multiply ?? false) ? 1 : this.zoomScaleArray[index],
850              y: this.options.type === 'capsule' && (this.options.multiply ?? false) ? 1 : this.zoomScaleArray[index]
851            })
852            .layoutWeight(1)
853            .padding(0)
854            .onSizeChange((_, newValue) => {
855              this.buttonItemsSize[index] = { width: newValue.width, height: this.buttonItemsSize[index].height }
856              //measure position
857              if (newValue.width) {
858                this.buttonItemsPosition[index] = {
859                  start: LengthMetrics.vp(Number.parseFloat(this.options.componentPadding.toString()) +
860                    (Number.parseFloat(newValue.width.toString()) + 1) * index),
861                  top: LengthMetrics.px(Math.floor(this.getUIContext()
862                    .vp2px(Number.parseFloat(this.options.componentPadding.toString()))))
863                }
864              }
865            })
866            .stateStyles({
867              normal: {
868                .overlay(undefined)
869              },
870              focused: {
871                .overlay(this.focusStack(index), {
872                  align: Alignment.Center
873                })
874              }
875            })
876            .onFocus(() => {
877              this.focusIndex = index
878            })
879            .gesture(TapGesture().onAction(() => {
880              if (this.options.type === 'capsule' && (this.options.multiply ?? false)) {
881                if (this.selectedIndexes.indexOf(index) === -1) {
882                  this.selectedIndexes.push(index)
883                } else {
884                  this.selectedIndexes.splice(this.selectedIndexes.indexOf(index), 1)
885                }
886              } else {
887                this.selectedIndexes[0] = index
888              }
889            }))
890            .onTouch((event: TouchEvent) => {
891              if (event.source !== SourceType.TouchScreen) {
892                return
893              }
894              if (event.type === TouchType.Down) {
895                animateTo({ curve: curves.interpolatingSpring(10, 1, 410, 38) }, () => {
896                  this.zoomScaleArray[index] = 0.95
897                })
898              } else if (event.type === TouchType.Up) {
899                animateTo({ curve: curves.interpolatingSpring(10, 1, 410, 38) }, () => {
900                  this.zoomScaleArray[index] = 1
901                })
902              }
903            })
904            .onHover((isHover: boolean) => {
905              this.hoverArray[index] = isHover
906              if (isHover) {
907                animateTo({ duration: 250, curve: Curve.Friction }, () => {
908                  this.hoverColorArray[index].hoverColor = (segmentButtonTheme.HOVER_COLOR)
909                })
910              } else {
911                animateTo({ duration: 250, curve: Curve.Friction }, () => {
912                  this.hoverColorArray[index].hoverColor = Color.Transparent
913                })
914              }
915            })
916            .onMouse((event: MouseEvent) => {
917              switch (event.action) {
918                case MouseAction.Press:
919                  animateTo({ curve: curves.springMotion(0.347, 0.99) }, () => {
920                    this.zoomScaleArray[index] = 0.95
921                  })
922                  animateTo({ duration: 100, curve: Curve.Sharp }, () => {
923                    this.pressArray[index] = true
924                  })
925                  break;
926                case MouseAction.Release:
927                  animateTo({ curve: curves.springMotion(0.347, 0.99) }, () => {
928                    this.zoomScaleArray[index] = 1
929                  })
930                  animateTo({ duration: 100, curve: Curve.Sharp }, () => {
931                    this.pressArray[index] = false
932                  })
933                  break;
934              }
935            })
936          }
937        })
938      }
939      .direction(this.options.direction)
940      .focusScopeId(this.groupId, true)
941      .padding(this.options.componentPadding)
942      .onSizeChange((_, newValue) => {
943        this.componentSize = { width: newValue.width, height: newValue.height }
944      })
945    }
946  }
947}
948
949@Observed
950class ItemProperty {
951  public fontColor: ResourceColor = segmentButtonTheme.FONT_COLOR
952  public fontSize: DimensionNoPercentage = segmentButtonTheme.FONT_SIZE
953  public fontWeight: FontWeight = FontWeight.Regular
954  public isSelected: boolean = false
955}
956
957@Component
958export struct SegmentButton {
959  @ObjectLink @Watch('onOptionsChange') options: SegmentButtonOptions
960  @Link @Watch('onSelectedChange') selectedIndexes: number[]
961  public onItemClicked?: Callback<number>
962  @Prop maxFontScale: number | Resource = DEFAULT_MAX_FONT_SCALE
963  @Provide componentSize: SizeOptions = { width: 0, height: 0 }
964  @Provide buttonBorderRadius: LocalizedBorderRadiuses[] = Array.from({
965    length: MAX_ITEM_COUNT
966  }, (_: Object, index): LocalizedBorderRadiuses => {
967    return {
968      topStart: LengthMetrics.vp(0),
969      topEnd: LengthMetrics.vp(0),
970      bottomStart: LengthMetrics.vp(0),
971      bottomEnd: LengthMetrics.vp(0)
972    }
973  })
974  @Provide buttonItemsSize: SizeOptions[] = Array.from({ length: MAX_ITEM_COUNT }, (_: Object, index): SizeOptions => {
975    return {}
976  })
977  @Provide @Watch('onItemsPositionChange') buttonItemsPosition: LocalizedEdges[] = Array.from({
978    length: MAX_ITEM_COUNT
979  }, (_: Object, index): LocalizedEdges => {
980    return {}
981  })
982  @Provide buttonItemsSelected: boolean[] = Array.from({ length: MAX_ITEM_COUNT }, (_: Object, index) => false)
983  @Provide buttonItemProperty: ItemProperty[] = Array.from({
984    length: MAX_ITEM_COUNT
985  }, (_: Object, index) => new ItemProperty())
986  @Provide focusIndex: number = -1
987  @Provide selectedItemPosition: LocalizedEdges = {}
988  @Provide zoomScaleArray: number[] = Array.from({ length: MAX_ITEM_COUNT }, (_: Object, index) => 1.0)
989  @State pressArray: boolean[] = Array.from({ length: MAX_ITEM_COUNT }, (_: Object, index) => false)
990  @State hoverArray: boolean[] = Array.from({ length: MAX_ITEM_COUNT }, (_: Object, index) => false)
991  @State hoverColorArray: HoverColorProperty[] = Array.from({
992    length: MAX_ITEM_COUNT
993  }, (_: Object, index) => new HoverColorProperty())
994  private doSelectedChangeAnimate: boolean = false
995  private isCurrentPositionSelected: boolean = false
996  private panGestureStartPoint: Point = { x: 0, y: 0 }
997  private isPanGestureMoved: boolean = false
998  @State shouldMirror: boolean = false
999
1000  onItemsPositionChange() {
1001    if (this.options === void 0 || this.options.buttons === void 0) {
1002      return
1003    }
1004    if (this.options.type === 'capsule') {
1005      this.options.onButtonsUpdated();
1006    }
1007    if (this.doSelectedChangeAnimate) {
1008      this.updateAnimatedProperty(this.getSelectedChangeCurve())
1009    } else {
1010      this.updateAnimatedProperty(null)
1011    }
1012  }
1013
1014  setItemsSelected() {
1015    this.buttonItemsSelected.forEach((_, index) => {
1016      this.buttonItemsSelected[index] = false
1017    })
1018    if (this.options.type === 'capsule' && (this.options.multiply ?? false)) {
1019      this.selectedIndexes.forEach(index => this.buttonItemsSelected[index] = true)
1020    } else {
1021      this.buttonItemsSelected[this.selectedIndexes[0]] = true
1022    }
1023  }
1024
1025  updateSelectedIndexes() {
1026    if (this.selectedIndexes === void 0) {
1027      this.selectedIndexes = []
1028    }
1029    if (this.options.type === 'tab' && this.selectedIndexes.length === 0) {
1030      this.selectedIndexes[0] = 0
1031    }
1032    if (this.selectedIndexes.length > 1) {
1033      if (this.options.type === 'tab') {
1034        this.selectedIndexes = [0]
1035      }
1036      if (this.options.type === 'capsule' && !(this.options.multiply ?? false)) {
1037        this.selectedIndexes = []
1038      }
1039    }
1040    let invalid = this.selectedIndexes.some(index => {
1041      return (index === void 0 || index < 0 || (this.options.buttons && index >= this.options.buttons.length))
1042    })
1043    if (invalid) {
1044      if (this.options.type === 'tab') {
1045        this.selectedIndexes = [0]
1046      } else {
1047        this.selectedIndexes = []
1048      }
1049    }
1050  }
1051
1052  onOptionsChange() {
1053    if (this.options === void 0 || this.options.buttons === void 0) {
1054      return
1055    }
1056    this.shouldMirror = this.isShouldMirror()
1057    this.updateSelectedIndexes()
1058    this.setItemsSelected()
1059    this.updateAnimatedProperty(null)
1060  }
1061
1062  onSelectedChange() {
1063    if (this.options === void 0 || this.options.buttons === void 0) {
1064      return
1065    }
1066    this.updateSelectedIndexes()
1067    this.setItemsSelected()
1068    if (this.doSelectedChangeAnimate) {
1069      this.updateAnimatedProperty(this.getSelectedChangeCurve())
1070    } else {
1071      this.updateAnimatedProperty(null)
1072    }
1073  }
1074
1075  aboutToAppear() {
1076    if (this.options === void 0 || this.options.buttons === void 0) {
1077      return
1078    }
1079    this.options.onButtonsChange = () => {
1080      if (this.options.type === 'tab') {
1081        this.selectedIndexes = [0]
1082      } else {
1083        this.selectedIndexes = []
1084      }
1085    }
1086    this.shouldMirror = this.isShouldMirror()
1087    this.updateSelectedIndexes()
1088    this.setItemsSelected()
1089    this.updateAnimatedProperty(null)
1090  }
1091
1092  private isMouseWheelScroll(event: GestureEvent) {
1093    return event.source === SourceType.Mouse && !this.isPanGestureMoved
1094  }
1095
1096  private isMovedFromPanGestureStartPoint(x: number, y: number) {
1097    return !nearEqual(x, this.panGestureStartPoint.x) || !nearEqual(y, this.panGestureStartPoint.y)
1098  }
1099
1100  private isShouldMirror(): boolean {
1101    if (this.options.direction == Direction.Rtl) {
1102      return true
1103    }
1104    // 获取系统语言
1105    try {
1106      let systemLanguage: string = I18n.System.getSystemLanguage();
1107      if (systemLanguage === 'ug' && this.options.direction != Direction.Ltr) { // 维吾尔语 非ltr模式
1108        return true
1109      }
1110    } catch (error) {
1111      console.error(`Ace SegmentButton getSystemLanguage, error: ${error.toString()}`);
1112    }
1113    return false
1114  }
1115
1116  build() {
1117    Stack() {
1118      if (this.options !== void 0 && this.options.buttons != void 0) {
1119        if (this.options.type === 'capsule' && (this.options.multiply ?? false)) {
1120          MultiSelectBackground({
1121            optionsArray: this.options.buttons,
1122            options: this.options,
1123          })
1124        } else {
1125          Stack() {
1126            if (this.options.buttons !== void 0 && this.options.buttons.length > 1) {
1127              Row({ space: 1 }) {
1128                ForEach(this.options.buttons, (item: SegmentButtonItemOptions, index) => {
1129                  if (index < MAX_ITEM_COUNT) {
1130                    Stack() {
1131                      PressAndHoverEffect({
1132                        pressIndex: index,
1133                        colorProperty: this.hoverColorArray[index],
1134                        press: this.pressArray[index],
1135                        hover: this.hoverArray[index],
1136                        options: this.options,
1137                      })
1138                    }
1139                    .direction(this.options.direction)
1140                    .scale({
1141                      x: this.options.type === 'capsule' && (this.options.multiply ?? false) ? 1 : this.zoomScaleArray[index],
1142                      y: this.options.type === 'capsule' && (this.options.multiply ?? false) ? 1 : this.zoomScaleArray[index]
1143                    })
1144                  }
1145                })
1146              }.direction(this.options.direction)
1147            }
1148          }
1149          .direction(this.options.direction)
1150          .size(this.componentSize)
1151          .backgroundColor(this.options.backgroundColor ?? segmentButtonTheme.BACKGROUND_COLOR)
1152          .borderRadius(this.options.iconTextBackgroundRadius ?? this.componentSize.height as number / 2)
1153          .backgroundBlurStyle(this.options.backgroundBlurStyle)
1154        }
1155        Stack() {
1156          if (this.options.type === 'capsule' && (this.options.multiply ?? false)) {
1157            MultiSelectItemArray({
1158              optionsArray: this.options.buttons,
1159              options: this.options,
1160              selectedIndexes: $selectedIndexes
1161            })
1162          } else {
1163            SelectItem({
1164              optionsArray: this.options.buttons,
1165              options: this.options,
1166              selectedIndexes: $selectedIndexes
1167            })
1168          }
1169        }
1170        .direction(this.options.direction)
1171        .size(this.componentSize)
1172        .animation({ duration: 0 })
1173        .borderRadius((this.options.type === 'capsule' && (this.options.multiply ?? false) ?
1174        this.options.iconTextRadius : this.options.iconTextBackgroundRadius) ??
1175          this.componentSize.height as number / 2)
1176        .clip(true)
1177
1178        SegmentButtonItemArrayComponent({
1179          pressArray: this.pressArray,
1180          hoverArray: this.hoverArray,
1181          hoverColorArray: this.hoverColorArray,
1182          optionsArray: this.options.buttons,
1183          options: this.options,
1184          selectedIndexes: $selectedIndexes,
1185          maxFontScale: this.getMaxFontSize()
1186        })
1187      }
1188    }
1189    .direction(this.options ? this.options.direction : undefined)
1190    .onBlur(() => {
1191      this.focusIndex = -1
1192    })
1193    .onKeyEvent((event: KeyEvent) => {
1194      if (this.options === void 0 || this.options.buttons === void 0) {
1195        return
1196      }
1197      if (event.type === KeyType.Down) {
1198        if (event.keyCode === KeyCode.KEYCODE_SPACE || event.keyCode === KeyCode.KEYCODE_ENTER ||
1199          event.keyCode === KeyCode.KEYCODE_NUMPAD_ENTER) {
1200          if (this.options.type === 'capsule' && (this.options.multiply ?? false)) {
1201            if (this.selectedIndexes.indexOf(this.focusIndex) === -1) {
1202              // Select
1203              this.selectedIndexes.push(this.focusIndex)
1204            } else {
1205              // Unselect
1206              this.selectedIndexes.splice(this.selectedIndexes.indexOf(this.focusIndex), 1)
1207            }
1208          } else {
1209            // Pressed
1210            this.selectedIndexes[0] = this.focusIndex
1211          }
1212        }
1213      }
1214    })
1215    .accessibilityLevel('no')
1216    .priorityGesture(
1217      GestureGroup(GestureMode.Parallel,
1218        TapGesture()
1219          .onAction((event: GestureEvent) => {
1220            this.focusIndex = -1
1221            let fingerInfo = event.fingerList.find(Boolean)
1222            if (fingerInfo === void 0) {
1223              return
1224            }
1225            if (this.options === void 0 || this.options.buttons === void 0) {
1226              return
1227            }
1228            let selectedInfo = fingerInfo.localX
1229
1230            let buttonLength: number = Math.min(this.options.buttons.length, this.buttonItemsSize.length)
1231            for (let i = 0; i < buttonLength; i++) {
1232              selectedInfo = selectedInfo - (this.buttonItemsSize[i].width as number)
1233              if (selectedInfo >= 0) {
1234                continue
1235              }
1236              this.doSelectedChangeAnimate =
1237                this.selectedIndexes[0] > Math.min(this.options.buttons.length,
1238                  this.buttonItemsSize.length) ? false : true
1239
1240              let realClickIndex: number = this.isShouldMirror() ? buttonLength - 1 - i : i
1241              if (this.onItemClicked) {
1242                this.onItemClicked(realClickIndex)
1243              }
1244              if (this.options.type === 'capsule' && (this.options.multiply ?? false)) {
1245                let selectedIndex: number = this.selectedIndexes.indexOf(realClickIndex)
1246                if (selectedIndex === -1) {
1247                  this.selectedIndexes.push(realClickIndex)
1248                } else {
1249                  this.selectedIndexes.splice(selectedIndex, 1)
1250                }
1251              } else {
1252                this.selectedIndexes[0] = realClickIndex
1253              }
1254              this.doSelectedChangeAnimate = false
1255              break
1256            }
1257          }),
1258        SwipeGesture()
1259          .onAction((event: GestureEvent) => {
1260            if (this.options === void 0 || this.options.buttons === void 0 || event.sourceTool === SourceTool.TOUCHPAD) {
1261              return
1262            }
1263            if (this.options.type === 'capsule' && (this.options.multiply ?? false)) {
1264              // Non swipe gesture in multi-select mode
1265              return
1266            }
1267            if (this.isCurrentPositionSelected) {
1268              return
1269            }
1270            if (event.angle > 0 && this.selectedIndexes[0] !== Math.min(this.options.buttons.length,
1271              this.buttonItemsSize.length) - 1) {
1272              // Move to next
1273              this.doSelectedChangeAnimate = true
1274              this.selectedIndexes[0] = this.selectedIndexes[0] + 1
1275              this.doSelectedChangeAnimate = false
1276            } else if (event.angle < 0 && this.selectedIndexes[0] !== 0) {
1277              // Move to previous
1278              this.doSelectedChangeAnimate = true
1279              this.selectedIndexes[0] = this.selectedIndexes[0] - 1
1280              this.doSelectedChangeAnimate = false
1281            }
1282          }),
1283        PanGesture()
1284          .onActionStart((event: GestureEvent) => {
1285            if (this.options === void 0 || this.options.buttons === void 0) {
1286              return
1287            }
1288            if (this.options.type === 'capsule' && (this.options.multiply ?? false)) {
1289              // Non drag gesture in multi-select mode
1290              return
1291            }
1292            let fingerInfo = event.fingerList.find(Boolean)
1293            if (fingerInfo === void 0) {
1294              return
1295            }
1296            let selectedInfo = fingerInfo.localX
1297            this.panGestureStartPoint = { x: fingerInfo.globalX, y: fingerInfo.globalY }
1298            this.isPanGestureMoved = false
1299            for (let i = 0; i < Math.min(this.options.buttons.length, this.buttonItemsSize.length); i++) {
1300              selectedInfo = selectedInfo - (this.buttonItemsSize[i].width as number)
1301              if (selectedInfo < 0) {
1302                this.isCurrentPositionSelected = i === this.selectedIndexes[0] ? true : false
1303                break
1304              }
1305            }
1306          })
1307          .onActionUpdate((event: GestureEvent) => {
1308            if (this.options === void 0 || this.options.buttons === void 0) {
1309              return
1310            }
1311            if (this.options.type === 'capsule' && (this.options.multiply ?? false)) {
1312              // Non drag gesture in multi-select mode
1313              return
1314            }
1315            if (!this.isCurrentPositionSelected) {
1316              return
1317            }
1318            let fingerInfo = event.fingerList.find(Boolean)
1319            if (fingerInfo === void 0) {
1320              return
1321            }
1322            let selectedInfo = fingerInfo.localX
1323            if (!this.isPanGestureMoved && this.isMovedFromPanGestureStartPoint(fingerInfo.globalX,
1324              fingerInfo.globalY)) {
1325              this.isPanGestureMoved = true
1326            }
1327            for (let i = 0; i < Math.min(this.options.buttons.length, this.buttonItemsSize.length); i++) {
1328              selectedInfo = selectedInfo - (this.buttonItemsSize[i].width as number)
1329              if (selectedInfo < 0) {
1330                this.doSelectedChangeAnimate = true
1331                this.selectedIndexes[0] = i
1332                this.doSelectedChangeAnimate = false
1333                break
1334              }
1335            }
1336            this.zoomScaleArray.forEach((_, index) => {
1337              if (index === this.selectedIndexes[0]) {
1338                animateTo({ curve: curves.interpolatingSpring(10, 1, 410, 38) }, () => {
1339                  this.zoomScaleArray[index] = 0.95
1340                })
1341              } else {
1342                animateTo({ curve: curves.interpolatingSpring(10, 1, 410, 38) }, () => {
1343                  this.zoomScaleArray[index] = 1
1344                })
1345              }
1346            })
1347          })
1348          .onActionEnd((event: GestureEvent) => {
1349            if (this.options === void 0 || this.options.buttons === void 0) {
1350              return
1351            }
1352            if (this.options.type === 'capsule' && (this.options.multiply ?? false)) {
1353              // Non drag gesture in multi-select mode
1354              return
1355            }
1356            let fingerInfo = event.fingerList.find(Boolean)
1357            if (fingerInfo === void 0) {
1358              return
1359            }
1360            if (!this.isPanGestureMoved && this.isMovedFromPanGestureStartPoint(fingerInfo.globalX,
1361              fingerInfo.globalY)) {
1362              this.isPanGestureMoved = true
1363            }
1364            if (this.isMouseWheelScroll(event)) {
1365              let offset = event.offsetX !== 0 ? event.offsetX : event.offsetY
1366              this.doSelectedChangeAnimate = true
1367              if (offset > 0 && this.selectedIndexes[0] > 0) {
1368                this.selectedIndexes[0] -= 1
1369              } else if (offset < 0 && this.selectedIndexes[0] < Math.min(this.options.buttons.length,
1370                this.buttonItemsSize.length) - 1) {
1371                this.selectedIndexes[0] += 1
1372              }
1373              this.doSelectedChangeAnimate = false
1374            }
1375            animateTo({ curve: curves.interpolatingSpring(10, 1, 410, 38) }, () => {
1376              this.zoomScaleArray[this.selectedIndexes[0]] = 1
1377            })
1378            this.isCurrentPositionSelected = false
1379          })
1380      )
1381    )
1382  }
1383
1384  getMaxFontSize(): number {
1385    if (typeof this.maxFontScale === void 0) {
1386      return DEFAULT_MAX_FONT_SCALE;
1387    }
1388    if (typeof this.maxFontScale === 'number') {
1389      return Math.max(Math.min(this.maxFontScale, MAX_MAX_FONT_SCALE), MIN_MAX_FONT_SCALE);
1390    }
1391    const resourceManager = this.getUIContext().getHostContext()?.resourceManager;
1392    if (!resourceManager) {
1393      return DEFAULT_MAX_FONT_SCALE;
1394    }
1395    try {
1396      return resourceManager.getNumber(this.maxFontScale.id);
1397    } catch (error) {
1398      console.error(`Ace SegmentButton getMaxFontSize, error: ${error.toString()}`);
1399      return DEFAULT_MAX_FONT_SCALE;
1400    }
1401  }
1402
1403  getSelectedChangeCurve(): ICurve | null {
1404    if (this.options.type === 'capsule' && (this.options.multiply ?? false)) {
1405      return null
1406    }
1407    return curves.springMotion(0.347, 0.99)
1408  }
1409
1410  updateAnimatedProperty(curve: ICurve | null) {
1411    let setAnimatedPropertyFunc = () => {
1412      this.selectedItemPosition = this.selectedIndexes.length === 0 ? {
1413      } : this.buttonItemsPosition[this.selectedIndexes[0]]
1414      this.buttonItemsSelected.forEach((selected, index) => {
1415        this.buttonItemProperty[index].fontColor = selected ?
1416          this.options.selectedFontColor ?? (this.options.type === 'tab' ?
1417          segmentButtonTheme.TAB_SELECTED_FONT_COLOR : segmentButtonTheme.CAPSULE_SELECTED_FONT_COLOR) :
1418          this.options.fontColor ?? segmentButtonTheme.FONT_COLOR
1419      })
1420    }
1421    if (curve) {
1422      animateTo({ curve: curve }, setAnimatedPropertyFunc)
1423    } else {
1424      setAnimatedPropertyFunc()
1425    }
1426    this.buttonItemsSelected.forEach((selected, index) => {
1427      this.buttonItemProperty[index].fontSize = selected ? this.options.selectedFontSize ??
1428      segmentButtonTheme.SELECTED_FONT_SIZE : this.options.fontSize ?? segmentButtonTheme.FONT_SIZE
1429      this.buttonItemProperty[index].fontWeight = selected ? this.options.selectedFontWeight ?? FontWeight.Medium :
1430        this.options.fontWeight ?? FontWeight.Regular
1431      this.buttonItemProperty[index].isSelected = selected
1432    })
1433  }
1434}