• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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