1/* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import measure from '@ohos.measure'; 17import Curves from '@ohos.curves'; 18import { ColorMetrics, LengthMetrics, LengthUnit } from '@ohos.arkui.node'; 19 20export enum ArcButtonPosition { 21 TOP_EDGE = 0, 22 BOTTOM_EDGE = 1 23} 24 25export enum ArcButtonStyleMode { 26 EMPHASIZED_LIGHT = 0, 27 EMPHASIZED_DARK = 1, 28 NORMAL_LIGHT = 2, 29 NORMAL_DARK = 3, 30 CUSTOM = 4 31} 32 33export enum ArcButtonStatus { 34 NORMAL = 0, 35 PRESSED = 1, 36 DISABLED = 2 37} 38 39interface CommonArcButtonOptions { 40 /** 41 * 弧形按钮位置类型属性,默认为下弧形按钮 42 */ 43 position?: ArcButtonPosition 44 /** 45 *强调、普通01、普通02、警告、自定义 默认是强调状态 46 */ 47 styleMode?: ArcButtonStyleMode 48 /** 49 *初始态、按压、不可用形态,默认为初始态 50 */ 51 status?: ArcButtonStatus 52 /** 53 *文字 54 */ 55 label?: ResourceStr 56 /** 57 *背景模糊能力 58 */ 59 backgroundBlurStyle?: BlurStyle 60 /** 61 *按钮背景色 62 */ 63 backgroundColor?: ColorMetrics 64 /** 65 *按钮阴影色 66 */ 67 shadowColor?: ColorMetrics 68 /** 69 *打开关闭弧形按钮阴影 70 */ 71 shadowEnabled?: boolean 72 /** 73 *字体大小 74 */ 75 fontSize?: LengthMetrics 76 /** 77 *字体颜色 78 */ 79 fontColor?: ColorMetrics 80 /** 81 *字体按压颜色 82 */ 83 pressedFontColor?: ColorMetrics 84 /** 85 *文字样式 86 */ 87 fontStyle?: FontStyle 88 /** 89 *文字字体族 90 */ 91 fontFamily?: string | Resource 92 /** 93 *文字到边框的距离 94 */ 95 fontMargin?: LocalizedMargin 96 /** 97 * TouchEvent 98 */ 99 onTouch?: Callback<TouchEvent> 100 /** 101 * ClickEvent 102 */ 103 onClick?: Callback<ClickEvent> 104} 105 106 107class Constants { 108 /** 109 * 最大文字大小 110 */ 111 public static readonly MAX_FONT_SIZE = 19; 112 /** 113 * 最小文字大小 114 */ 115 public static readonly MIN_FONT_SIZE = 13; 116 /** 117 * 阴影半径 118 */ 119 public static readonly SHADOW_BLUR = 4; 120 /** 121 * Y偏移 122 */ 123 public static readonly SHADOW_OFFSET_Y = 3; 124 /** 125 * 按钮与边框距离 126 */ 127 public static readonly DISTANCE_FROM_BORDER = 1; 128 /** 129 * 文本间距 130 */ 131 public static readonly TEXT_HORIZONTAL_MARGIN = 24; 132 public static readonly TEXT_MARGIN_TOP = 10; 133 public static readonly TEXT_MARGIN_BOTTOM = 16; 134 public static readonly EMPHASIZED_NORMAL_BTN_COLOR = $r('sys.color.comp_background_emphasize'); 135 public static readonly EMPHASIZED_TEXT_COLOR = '#FFFFFF'; 136 public static readonly EMPHASIZED_PRESSED_BTN_COLOR = '#357FFF'; 137 public static readonly EMPHASIZED_DISABLE_BTN_COLOR = '#1F71FF'; 138 public static readonly EMPHASIZED_DISABLE_TEXT_COLOR = '#FFFFFF'; 139 public static readonly NORMAL_LIGHT_NORMAL_BTN_COLOR = '#17273F'; 140 public static readonly NORMAL_LIGHT_TEXT_COLOR = '#5EA1FF'; 141 public static readonly NORMAL_LIGHT_PRESSED_BTN_COLOR = '#2E3D52'; 142 public static readonly NORMAL_LIGHT_DISABLE_BTN_COLOR = '#17273F'; 143 public static readonly NORMAL_LIGHT_DISABLE_TEXT_COLOR = '#995ea1ff'; 144 public static readonly NORMAL_DARK_NORMAL_BTN_COLOR = '#252525'; 145 public static readonly NORMAL_DARK_TEXT_COLOR = '#5EA1FF'; 146 public static readonly NORMAL_DARK_PRESSED_BTN_COLOR = '#3B3B3B'; 147 public static readonly NORMAL_DARK_DISABLE_BTN_COLOR = '#262626'; 148 public static readonly NORMAL_DARK_DISABLE_TEXT_COLOR = '#995ea1ff'; 149 public static readonly EMPHASIZEWARN_NORMAL_BTN_COLOR = '#BF2629'; 150 public static readonly EMPHASIZEWARN_TEXT_COLOR = '#FFFFFF'; 151 public static readonly EMPHASIZEWARN_PRESSED_BTN_COLOR = '#C53C3E'; 152 public static readonly EMPHASIZEWARN_DISABLE_BTN_COLOR = '#4C0f10'; 153 public static readonly EMPHASIZEWARN_DISABLE_TEXT_COLOR = '#99FFFFFF'; 154 public static readonly DEFAULT_TRANSPARENCY = 0.4; 155} 156 157interface ArcButtonThemeInterface { 158 /** 159 * 弧形按钮高度 160 */ 161 BUTTON_HEIGHT: number, 162 163 /** 164 * 辅助圆半径 165 */ 166 ARC_CIRCLE_DIAMETER: number, 167 168 /** 169 * 表盘直径 170 */ 171 DIAL_CIRCLE_DIAMETER: number, 172 173 /** 174 * 弧形按钮倒角圆半径 175 */ 176 CHAMFER_CIRCLE_RADIUS: number, 177} 178 179@ObservedV2 180export class ArcButtonOptions { 181 @Trace public position: ArcButtonPosition; 182 @Trace public styleMode: ArcButtonStyleMode; 183 @Trace public status: ArcButtonStatus; 184 @Trace public label: ResourceStr; 185 @Trace public backgroundBlurStyle: BlurStyle; 186 @Trace public backgroundColor: ColorMetrics; 187 @Trace public shadowColor: ColorMetrics; 188 @Trace public shadowEnabled: boolean; 189 @Trace public fontSize: LengthMetrics; 190 @Trace public fontColor: ColorMetrics; 191 @Trace public pressedFontColor: ColorMetrics; 192 @Trace public fontStyle: FontStyle; 193 @Trace public fontFamily: string | Resource; 194 @Trace public fontMargin: LocalizedMargin; 195 @Trace public onTouch?: Callback<TouchEvent>; 196 @Trace public onClick?: Callback<ClickEvent>; 197 198 constructor(options: CommonArcButtonOptions) { 199 this.position = options.position ?? ArcButtonPosition.BOTTOM_EDGE; 200 this.styleMode = options.styleMode ?? ArcButtonStyleMode.EMPHASIZED_LIGHT; 201 this.status = options.status ?? ArcButtonStatus.NORMAL; 202 this.label = options.label ?? ''; 203 this.backgroundBlurStyle = options.backgroundBlurStyle ?? BlurStyle.NONE; 204 this.backgroundColor = options.backgroundColor ?? ColorMetrics.resourceColor(Color.Black); 205 this.shadowColor = options.shadowColor ?? ColorMetrics.resourceColor('#000000'); 206 this.shadowEnabled = options.shadowEnabled ?? false; 207 this.fontSize = options.fontSize ?? new LengthMetrics(Constants.MAX_FONT_SIZE); 208 this.fontColor = options.fontColor ?? ColorMetrics.resourceColor(Color.White); 209 this.pressedFontColor = options.pressedFontColor ?? ColorMetrics.resourceColor(Color.White); 210 this.fontStyle = options.fontStyle ?? FontStyle.Normal; 211 this.fontFamily = options.fontFamily ?? ''; 212 this.fontMargin = options.fontMargin ?? { 213 start: LengthMetrics.vp(Constants.TEXT_HORIZONTAL_MARGIN), 214 top: LengthMetrics.vp(Constants.TEXT_MARGIN_TOP), 215 end: LengthMetrics.vp(Constants.TEXT_HORIZONTAL_MARGIN), 216 bottom: LengthMetrics.vp(Constants.TEXT_MARGIN_BOTTOM) 217 } 218 this.onTouch = options.onTouch ?? (() => { 219 }) 220 this.onClick = options.onClick ?? (() => { 221 }) 222 } 223} 224 225@ComponentV2 226export struct ArcButton { 227 @Require @Param options: ArcButtonOptions; 228 @Local private canvasWidth: number = 0; 229 @Local private canvasHeight: number = 0; 230 @Local private scaleX: number = 1; 231 @Local private scaleY: number = 1; 232 @Local private btnColor: ColorMetrics = ColorMetrics.resourceColor(Color.Black); 233 @Local private textWidth: number = 0; 234 @Local private textHeight: number = 0; 235 @Local private fontColor: ColorMetrics = ColorMetrics.resourceColor(Color.White); 236 @Local private isExceed: boolean = false; 237 @Local private pathString: string = ''; 238 @Local private fontSize: string = '' 239 private btnNormalColor: ColorMetrics = ColorMetrics.resourceColor(Color.Black); 240 private btnPressColor: ColorMetrics = ColorMetrics.resourceColor(Color.Black); 241 private btnDisableColor: ColorMetrics = ColorMetrics.resourceColor(Color.Black); 242 private textNormalColor: ColorMetrics = ColorMetrics.resourceColor(Color.White); 243 private textDisableColor: ColorMetrics = ColorMetrics.resourceColor(Color.White); 244 private isUp: boolean = false; 245 private curves: ICurve = Curves.interpolatingSpring(10, 1, 350, 35); 246 private scaleValue: number = 1; 247 private textPressColor: ColorMetrics = ColorMetrics.resourceColor(Color.White); 248 private arcButtonTheme: ArcButtonThemeInterface = { 249 BUTTON_HEIGHT: this.getArcButtonThemeVpValue($r('sys.float.arc_button_height')), 250 ARC_CIRCLE_DIAMETER: this.getArcButtonThemeVpValue($r('sys.float.arc_button_auxiliary_circle_diameter')), 251 DIAL_CIRCLE_DIAMETER: this.getArcButtonThemeVpValue($r('sys.float.arc_button_dial_circle_diameter')), 252 CHAMFER_CIRCLE_RADIUS: this.getArcButtonThemeVpValue($r('sys.float.arc_button_chamfer_radius')) 253 } 254 private dataProcessUtil: DataProcessUtil = new DataProcessUtil(this.arcButtonTheme); 255 256 @Monitor('options.label', 'options.type', 'options.fontSize', 'options.styleMode', 'options.status', 257 'options.backgroundColor', 'options.fontColor') 258 optionsChange() { 259 this.fontSize = this.cover(this.options.fontSize) 260 this.judgeTextWidth() 261 this.changeStatus() 262 } 263 264 changeStatus() { 265 switch (this.options.styleMode) { 266 case ArcButtonStyleMode.EMPHASIZED_LIGHT: 267 this.btnNormalColor = ColorMetrics.resourceColor(Constants.EMPHASIZED_NORMAL_BTN_COLOR); 268 this.textNormalColor = ColorMetrics.resourceColor(Constants.EMPHASIZED_TEXT_COLOR); 269 this.btnPressColor = ColorMetrics.resourceColor(Constants.EMPHASIZED_PRESSED_BTN_COLOR); 270 this.btnDisableColor = ColorMetrics.resourceColor(Constants.EMPHASIZED_DISABLE_BTN_COLOR); 271 this.textDisableColor = ColorMetrics.resourceColor(Constants.EMPHASIZED_DISABLE_TEXT_COLOR); 272 this.textPressColor = ColorMetrics.resourceColor(Constants.EMPHASIZED_TEXT_COLOR); 273 break; 274 275 case ArcButtonStyleMode.NORMAL_LIGHT: 276 this.btnNormalColor = ColorMetrics.resourceColor(Constants.NORMAL_LIGHT_NORMAL_BTN_COLOR); 277 this.textNormalColor = ColorMetrics.resourceColor(Constants.NORMAL_LIGHT_TEXT_COLOR); 278 this.btnPressColor = ColorMetrics.resourceColor(Constants.NORMAL_LIGHT_PRESSED_BTN_COLOR); 279 this.btnDisableColor = ColorMetrics.resourceColor(Constants.NORMAL_LIGHT_DISABLE_BTN_COLOR); 280 this.textDisableColor = ColorMetrics.resourceColor(Constants.NORMAL_LIGHT_DISABLE_TEXT_COLOR); 281 this.textPressColor = ColorMetrics.resourceColor(Constants.NORMAL_LIGHT_TEXT_COLOR); 282 break; 283 284 case ArcButtonStyleMode.NORMAL_DARK: 285 this.btnNormalColor = ColorMetrics.resourceColor(Constants.NORMAL_DARK_NORMAL_BTN_COLOR); 286 this.textNormalColor = ColorMetrics.resourceColor(Constants.NORMAL_DARK_TEXT_COLOR); 287 this.btnPressColor = ColorMetrics.resourceColor(Constants.NORMAL_DARK_PRESSED_BTN_COLOR); 288 this.btnDisableColor = ColorMetrics.resourceColor(Constants.NORMAL_DARK_DISABLE_BTN_COLOR); 289 this.textDisableColor = ColorMetrics.resourceColor(Constants.NORMAL_DARK_DISABLE_TEXT_COLOR); 290 this.textPressColor = ColorMetrics.resourceColor(Constants.NORMAL_DARK_TEXT_COLOR); 291 break; 292 293 case ArcButtonStyleMode.EMPHASIZED_DARK: 294 this.btnNormalColor = ColorMetrics.resourceColor(Constants.EMPHASIZEWARN_NORMAL_BTN_COLOR); 295 this.textNormalColor = ColorMetrics.resourceColor(Constants.EMPHASIZEWARN_TEXT_COLOR); 296 this.btnPressColor = ColorMetrics.resourceColor(Constants.EMPHASIZEWARN_PRESSED_BTN_COLOR); 297 this.btnDisableColor = ColorMetrics.resourceColor(Constants.EMPHASIZEWARN_DISABLE_BTN_COLOR); 298 this.textDisableColor = ColorMetrics.resourceColor(Constants.EMPHASIZEWARN_DISABLE_TEXT_COLOR); 299 this.textPressColor = ColorMetrics.resourceColor(Constants.EMPHASIZEWARN_TEXT_COLOR); 300 break; 301 302 default: 303 this.btnNormalColor = this.options.backgroundColor; 304 this.textNormalColor = this.options.fontColor; 305 this.btnPressColor = this.options.backgroundColor; 306 this.textPressColor = this.options.pressedFontColor; 307 break; 308 } 309 if (this.options.status === ArcButtonStatus.DISABLED) { 310 this.btnColor = this.btnDisableColor; 311 this.fontColor = this.textDisableColor; 312 } else { 313 this.btnColor = this.btnNormalColor; 314 this.fontColor = this.textNormalColor; 315 } 316 } 317 318 /** 319 * 初始化数据 320 */ 321 private initValues() { 322 this.isUp = this.options.position == ArcButtonPosition.TOP_EDGE; 323 this.btnColor = this.options.backgroundColor; 324 this.fontColor = this.options.fontColor; 325 this.curves = Curves.interpolatingSpring(10, 1, 350, 35); 326 this.scaleValue = 0.95; 327 this.changeStatus(); 328 } 329 330 private getArcButtonThemeVpValue(res: Resource): number { 331 if (!res) { 332 return 0 333 } 334 let metrics = LengthMetrics.resource(res) 335 let value = metrics.value 336 switch (metrics.unit) { 337 case LengthUnit.PX: 338 return px2vp(value) 339 340 case LengthUnit.LPX: 341 return px2vp(lpx2px(value)) 342 343 case LengthUnit.FP: 344 return px2vp(fp2px(value)) 345 } 346 return value 347 } 348 349 /** 350 * 判断是否超出文本框宽度 351 */ 352 private judgeTextWidth() { 353 const measureTextWidth = measure.measureText({ 354 textContent: this.options.label, 355 fontStyle: this.options.fontStyle, 356 fontFamily: this.options.fontFamily, 357 fontWeight: FontWeight.Medium, 358 maxLines: 1, 359 fontSize: `${Constants.MIN_FONT_SIZE}fp` 360 }) 361 this.isExceed = measureTextWidth > this.getUIContext().vp2px(this.textWidth); 362 } 363 364 aboutToAppear() { 365 if (this.arcButtonTheme.BUTTON_HEIGHT === 0) { 366 console.error("arcbutton can't obtain sys float value.") 367 return 368 } 369 this.initValues(); 370 this.dataProcessUtil.initData(); 371 const pathData = this.dataProcessUtil.calculate(); 372 this.generatePath(pathData); 373 } 374 375 private calculateActualPosition(pos: ArcButtonPoint, canvasTopPos: ArcButtonPoint): ArcButtonPoint { 376 const x = this.getUIContext().vp2px(pos.x - canvasTopPos.x); 377 const y = this.getUIContext().vp2px(pos.y - canvasTopPos.y); 378 return new ArcButtonPoint(x, y); 379 } 380 381 private generatePath(data: AllPoints | null) { 382 if (data === null) { 383 return 384 } 385 this.canvasWidth = data.btnWidth + Constants.SHADOW_BLUR * 2; 386 this.canvasHeight = data.btnHeight + Constants.DISTANCE_FROM_BORDER * 2; 387 388 const margin = this.options.fontMargin; 389 const start = margin?.start?.value ?? 0; 390 const end = margin?.end?.value ?? 0; 391 const top = margin?.top?.value ?? 0; 392 const bottom = margin?.bottom?.value ?? 0; 393 this.textWidth = data.btnWidth - start - end; 394 this.textHeight = data.btnHeight - top - bottom; 395 this.judgeTextWidth(); 396 const canvasLeftTopPoint = data.canvasLeftTop; 397 canvasLeftTopPoint.x -= Constants.SHADOW_BLUR; 398 canvasLeftTopPoint.y -= Constants.DISTANCE_FROM_BORDER; 399 400 const leftTopPoint = this.calculateActualPosition(data.leftTopPoint, canvasLeftTopPoint); 401 const upperArcCircleR: number = this.getUIContext().vp2px(this.arcButtonTheme.ARC_CIRCLE_DIAMETER / 2); 402 403 const rightTopPoint = this.calculateActualPosition(data.rightTopPoint, canvasLeftTopPoint); 404 const chamferCircleR: number = this.getUIContext().vp2px(this.arcButtonTheme.CHAMFER_CIRCLE_RADIUS); 405 406 const rightBottomPoint = this.calculateActualPosition(data.rightBottomPoint, canvasLeftTopPoint); 407 const lowerArcCircleR: number = this.getUIContext().vp2px(this.arcButtonTheme.DIAL_CIRCLE_DIAMETER / 2); 408 409 const leftBottomPoint = this.calculateActualPosition(data.leftBottomPoint, canvasLeftTopPoint); 410 411 const pathStr = `M ${leftTopPoint.x} ${leftTopPoint.y} A ${upperArcCircleR} ${upperArcCircleR}, 0, 0, 0, 412 ${rightTopPoint.x} ${rightTopPoint.y}` + 413 `Q ${rightTopPoint.x - chamferCircleR * 1.2} ${rightTopPoint.y + 414 chamferCircleR * 0.6} ${rightBottomPoint.x} ${rightBottomPoint.y}` + 415 `A ${lowerArcCircleR} ${lowerArcCircleR}, 0, 0, 0, ${leftBottomPoint.x} 416 ${leftBottomPoint.y}` + 417 `Q ${leftTopPoint.x + chamferCircleR * 1.2} ${leftTopPoint.y + 418 chamferCircleR * 0.6} ${leftTopPoint.x} ${leftTopPoint.y}` 419 420 this.pathString = pathStr 421 } 422 423 @Builder 424 TextBuilderIsExceed() { 425 Text(this.options.label) 426 .width(this.textWidth) 427 .height(this.textHeight) 428 .fontColor(this.fontColor.color) 429 .fontSize(this.fontSize) 430 .maxLines(1) 431 .textAlign(TextAlign.Center) 432 .fontWeight(FontWeight.Medium) 433 .fontStyle(this.options.fontStyle) 434 .fontFamily(this.options.fontFamily) 435 .backgroundColor(Color.Transparent) 436 .textOverflow({ overflow: TextOverflow.MARQUEE }) 437 .margin({ 438 start: this.options.fontMargin.start, 439 top: this.isUp ? this.options.fontMargin.bottom : this.options.fontMargin.top, 440 end: this.options.fontMargin.end, 441 bottom: this.options.fontMargin.bottom 442 }) 443 } 444 445 @Builder 446 TextBuilderNormal() { 447 Text(this.options.label) 448 .width(this.textWidth) 449 .height(this.textHeight) 450 .textAlign(TextAlign.Center) 451 .fontColor(this.fontColor.color) 452 .maxFontSize(`${Constants.MAX_FONT_SIZE}fp`) 453 .minFontSize(`${Constants.MIN_FONT_SIZE}fp`) 454 .fontWeight(FontWeight.Medium) 455 .fontStyle(this.options.fontStyle) 456 .fontFamily(this.options.fontFamily) 457 .maxLines(1) 458 .margin({ 459 start: this.options.fontMargin.start, 460 top: this.isUp ? this.options.fontMargin.bottom : this.options.fontMargin.top, 461 end: this.options.fontMargin.end, 462 bottom: this.options.fontMargin.bottom 463 }) 464 } 465 466 private cover(params: LengthMetrics): string { 467 switch (params.unit) { 468 case LengthUnit.VP: 469 return `${params.value}vp`; 470 case LengthUnit.PX: 471 return `${params.value}px`; 472 case LengthUnit.FP: 473 return `${params.value}fp`; 474 case LengthUnit.LPX: 475 return `${params.value}lpx`; 476 case LengthUnit.PERCENT: 477 return `${params.value}%`; 478 } 479 } 480 481 private getShadow(): ShadowOptions | undefined { 482 if (!this.options.shadowEnabled) { 483 return undefined; 484 } 485 return { 486 radius: Constants.SHADOW_BLUR, 487 color: this.options.shadowColor.color, 488 offsetY: Constants.SHADOW_OFFSET_Y 489 } 490 } 491 492 build() { 493 Stack({ alignContent: Alignment.Center }) { 494 Button({ type: ButtonType.Normal, stateEffect: true }) 495 .width('100%') 496 .height('100%') 497 .rotate({ angle: !this.isUp ? 0 : 180 }) 498 .clipShape(new Path({ commands: this.pathString })) 499 .backgroundColor(this.btnColor.color) 500 .backgroundBlurStyle(this.options.backgroundBlurStyle, undefined, { disableSystemAdaptation: true }) 501 .shadow(this.getShadow()) 502 if (this.isExceed) { 503 this.TextBuilderIsExceed() 504 } else { 505 this.TextBuilderNormal() 506 } 507 } 508 .enabled(this.options.status !== ArcButtonStatus.DISABLED) 509 .opacity((this.options.styleMode === ArcButtonStyleMode.EMPHASIZED_LIGHT && 510 this.options.status === ArcButtonStatus.DISABLED) ? Constants.DEFAULT_TRANSPARENCY : 1) 511 .animation({ curve: this.curves }) 512 .width(this.canvasWidth) 513 .height(this.canvasHeight) 514 .scale({ x: this.scaleX, y: this.scaleY, centerY: this.isUp ? 0 : this.canvasHeight }) 515 .onTouch((event: TouchEvent) => { 516 this.dealTouchEvent(event) 517 }) 518 .onClick((event: ClickEvent) => { 519 if (this.options.onClick) { 520 this.options.onClick(event) 521 } 522 }) 523 } 524 525 private dealTouchEvent(event: TouchEvent) { 526 const x = event.touches[0].windowX; 527 const y = event.touches[0].windowY; 528 if (this.options.onTouch) { 529 this.options.onTouch(event); 530 } 531 switch (event.type) { 532 case TouchType.Down: 533 this.scaleX = this.scaleValue; 534 this.scaleY = this.scaleValue; 535 this.btnColor = this.btnPressColor; 536 this.fontColor = this.textPressColor; 537 break; 538 case TouchType.Up: 539 this.scaleX = 1; 540 this.scaleY = 1; 541 this.btnColor = this.btnNormalColor; 542 this.fontColor = this.textNormalColor; 543 break; 544 default: 545 break; 546 } 547 } 548} 549 550class DataProcessUtil { 551 private dial: ArcButtonCircle = new ArcButtonCircle(0, 0, 0); 552 private arc: ArcButtonCircle = new ArcButtonCircle(0, 0, 0); 553 private height: number = 0; 554 private width: number = 0; 555 private arcButtonTheme: ArcButtonThemeInterface | undefined = undefined 556 557 constructor(theme: ArcButtonThemeInterface) { 558 this.arcButtonTheme = theme 559 } 560 561 initData() { 562 const dialRadius = this.arcButtonTheme!.DIAL_CIRCLE_DIAMETER / 2; 563 this.dial = new ArcButtonCircle(dialRadius, dialRadius, dialRadius); 564 565 const arcRadius = this.arcButtonTheme!.ARC_CIRCLE_DIAMETER / 2; 566 this.height = this.arcButtonTheme!.BUTTON_HEIGHT; 567 const arcX = this.dial.center.x; 568 const arcY = this.dial.center.y + dialRadius + arcRadius - this.height; 569 this.arc = new ArcButtonCircle(arcRadius, arcX, arcY); 570 } 571 572 calculate(): AllPoints { 573 const chamferCircleR = this.arcButtonTheme!.CHAMFER_CIRCLE_RADIUS; 574 const innerDial = new ArcButtonCircle(this.dial.radius - chamferCircleR, this.dial.center.x, this.dial.center.y); 575 const innerArc = new ArcButtonCircle(this.arc.radius - chamferCircleR, this.arc.center.x, this.arc.center.y); 576 const intersections = this.findCircleIntersections(innerArc, innerDial); 577 const tp1 = this.calculateIntersection(this.arc.center, this.arc.radius, intersections[0]); 578 const tp2 = this.calculateIntersection(this.arc.center, this.arc.radius, intersections[1]); 579 const tp3 = this.calculateIntersection(this.dial.center, this.dial.radius, intersections[1]); 580 const tp4 = this.calculateIntersection(this.dial.center, this.dial.radius, intersections[0]); 581 582 this.width = this.calculateDistance(intersections[0], intersections[1]) + chamferCircleR * 2; 583 const canvasLeftTop = new ArcButtonPoint(intersections[0].x - chamferCircleR, this.dial.center.y + 584 this.dial.radius - this.height); 585 586 return new AllPoints(this.width, this.height, tp2, tp1, tp3, tp4, canvasLeftTop); 587 } 588 589 /** 590 * 计算两点间距离 591 * @param point1 点1 592 * @param point2 点2 593 * @returns 距离 594 */ 595 calculateDistance(point1: ArcButtonPoint, point2: ArcButtonPoint): number { 596 return Math.sqrt((point2.x - point1.x) ** 2 + (point2.y - point1.y) ** 2); 597 } 598 599 calculateIntersection(circleCenter: ArcButtonPoint, circleRadius: number, point: ArcButtonPoint): ArcButtonPoint { 600 const h = circleCenter.x; 601 const k = circleCenter.y; 602 const x = point.x; 603 const y = point.y; 604 605 //计算直线斜率 606 let m: number = 0; 607 if (x !== h) { 608 m = (y - k) / (x - h); 609 } else { 610 m = -1; 611 } 612 613 //计算截距 614 let intercept: number = 0; 615 if (m !== -1) { 616 intercept = y - m * x; 617 } 618 619 //保存焦点位置 620 let resultPoint: ArcButtonPoint[] = [] 621 622 //判断斜率 623 if (m !== -1) { 624 const a = Math.pow(m, 2) + 1; 625 const b = 2 * (m * intercept - m * k - h); 626 const c = k ** 2 - circleRadius ** 2 + h ** 2 - 2 * intercept * k + intercept ** 2; 627 628 const x1 = (-b + (b ** 2 - 4 * a * c) ** 0.5) / (2 * a); 629 const x2 = (-b - (b ** 2 - 4 * a * c) ** 0.5) / (2 * a); 630 const y1: number = m * x1 + intercept; 631 const y2: number = m * x2 + intercept; 632 633 resultPoint = [new ArcButtonPoint(x1, y1), new ArcButtonPoint(x2, y2)]; 634 } else { 635 const x1 = h; 636 const y1 = k + circleRadius; 637 const y2 = k - circleRadius; 638 resultPoint = [new ArcButtonPoint(x1, y1), new ArcButtonPoint(x1, y2)]; 639 } 640 641 const d1 = this.calculateDistance(resultPoint[0], point); 642 const d2 = this.calculateDistance(resultPoint[1], point); 643 if (d1 < d2) { 644 return resultPoint[0]; 645 } else { 646 return resultPoint[1]; 647 } 648 } 649 650 /** 651 * 查找两圆的交点 652 * @param C1 第一个圆 653 * @param c2 第二个圆 654 * @returns 两圆相交的点的数组 655 */ 656 findCircleIntersections(firstCircus: ArcButtonCircle, secondCircus: ArcButtonCircle): ArcButtonPoint[] { 657 const firstCircusR = firstCircus.radius; 658 const firstCircusCenterX = firstCircus.center.x; 659 const firstCircusCenterY = firstCircus.center.y; 660 661 const secondCircusR = secondCircus.radius; 662 const secondCircusCenterX = secondCircus.center.x; 663 const secondCircusCenterY = secondCircus.center.y; 664 665 // 计算两个圆心之间的距离 666 const distance = Math.sqrt((firstCircusCenterX - secondCircusCenterX) ** 2 + (firstCircusCenterY - 667 secondCircusCenterY) ** 2); 668 669 // 检查异常情况 670 if (distance > firstCircusR + secondCircusR) { 671 //两个圆分离,不相交 672 return []; 673 } else if (distance < Math.abs(firstCircusR - secondCircusR)) { 674 //一个圆包含在另一个圆内,不相交 675 return []; 676 } else if (distance === 0 && firstCircusR === secondCircusR) { 677 //两个圆完全重合,具有无穷多交点 678 return []; 679 } 680 681 // 计算交点 682 const a = (firstCircusR ** 2 - secondCircusR ** 2 + distance ** 2) / (2 * distance); 683 const h = Math.sqrt(firstCircusR ** 2 - a ** 2); 684 685 // 中间变量 686 const x2 = firstCircusCenterX + a * (secondCircusCenterX - firstCircusCenterX) / distance; 687 const y2 = firstCircusCenterY + a * (secondCircusCenterY - firstCircusCenterY) / distance; 688 689 // 交点 690 let intersection1 = new ArcButtonPoint(x2 + h * (secondCircusCenterY - firstCircusCenterY) / distance, y2 - 691 h * (secondCircusCenterX - firstCircusCenterX) / distance); 692 let intersection2 = new ArcButtonPoint(x2 - h * (secondCircusCenterY - firstCircusCenterY) / distance, y2 + 693 h * (secondCircusCenterX - firstCircusCenterX) / distance); 694 695 if (intersection1.x > intersection2.x) { 696 const mid = intersection1; 697 intersection1 = intersection2; 698 intersection2 = mid; 699 } 700 701 return [intersection1, intersection2]; 702 } 703} 704 705class ArcButtonCircle { 706 public radius: number; 707 public center: ArcButtonPoint; 708 709 constructor(radius: number, x: number, y: number) { 710 this.radius = radius; 711 this.center = new ArcButtonPoint(x, y); 712 } 713} 714 715class ArcButtonPoint { 716 public x: number; 717 public y: number; 718 719 constructor(x: number, y: number) { 720 this.x = x; 721 this.y = y; 722 } 723} 724 725class AllPoints { 726 public btnWidth: number; 727 public btnHeight: number; 728 public leftTopPoint: ArcButtonPoint; 729 public rightTopPoint: ArcButtonPoint; 730 public leftBottomPoint: ArcButtonPoint; 731 public rightBottomPoint: ArcButtonPoint; 732 public canvasLeftTop: ArcButtonPoint; 733 734 constructor(btnWidth: number, 735 btnHeight: number, 736 leftTopPoint: ArcButtonPoint, 737 rightTopPoint: ArcButtonPoint, 738 leftBottomPoint: ArcButtonPoint, 739 rightBottomPoint: ArcButtonPoint, 740 canvasLeftTop: ArcButtonPoint) { 741 this.btnWidth = btnWidth; 742 this.btnHeight = btnHeight; 743 this.leftTopPoint = leftTopPoint; 744 this.rightTopPoint = rightTopPoint; 745 this.leftBottomPoint = leftBottomPoint; 746 this.rightBottomPoint = rightBottomPoint; 747 this.canvasLeftTop = canvasLeftTop; 748 } 749} 750