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}