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