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 { Theme } from '@ohos.arkui.theme'; 17import { LengthMetrics, LengthUnit } from '@ohos.arkui.node'; 18 19const EMPTY_STRING: string = ''; 20const MAX_PROGRESS: number = 100; 21const MAX_PERCENTAGE: string = '100%'; 22const MIN_PERCENTAGE: string = '0%'; 23const TEXT_OPACITY: number = 0.4; 24const BUTTON_NORMARL_WIDTH: number = 44; 25const BUTTON_NORMARL_HEIGHT: number = 28; 26const BUTTON_BORDER_RADIUS: number = 14; 27const TEXT_ENABLE: number = 1.0; 28 29 30// Set the key value for the basic component of skin changing corresponding to progressButton 31const PROGRESS_BUTTON_PROGRESS_KEY = 'progress_button_progress_key'; 32const PROGRESS_BUTTON_PRIMARY_FONT_KEY = 'progress_button_primary_font_key'; 33const PROGRESS_BUTTON_CONTAINER_BACKGROUND_COLOR_KEY = 'progress_button_container_background_color_key'; 34const PROGRESS_BUTTON_EMPHASIZE_SECONDARY_BUTTON_KEY = 'progress_button_emphasize_secondary_button_key'; 35 36@ComponentV2 37export struct ProgressButtonV2 { 38 @Param @Require progress: number; 39 @Param content: ResourceStr = EMPTY_STRING; 40 @Param @Once progressButtonWidth?: LengthMetrics = LengthMetrics.vp(BUTTON_NORMARL_WIDTH); 41 @Param onClicked: ClickCallback = () => { 42 }; 43 @Param isEnabled: boolean = true; 44 @Param colorOptions?: ProgressButtonV2Color | undefined = undefined; 45 @Param progressButtonRadius?: LengthMetrics | undefined = undefined; 46 @Local textProgress: string = EMPTY_STRING; 47 @Local isLoading: boolean = false; 48 @Local progressColor: ResourceColor = '#330A59F7' 49 @Local containerBorderColor: ResourceColor = '#330A59F7' 50 @Local containerBackgroundColor: ResourceColor = $r('sys.color.ohos_id_color_foreground_contrary') 51 @Local textHeight?: Length = BUTTON_NORMARL_HEIGHT; 52 @Local buttonBorderRadius?: number = BUTTON_BORDER_RADIUS; 53 54 onWillApplyTheme(theme: Theme) { 55 this.progressColor = theme.colors.compEmphasizeSecondary; 56 this.containerBorderColor = theme.colors.compEmphasizeSecondary; 57 this.containerBackgroundColor = theme.colors.iconOnFourth; 58 } 59 60 private getButtonProgress(): number { 61 if (this.progress < 0) { 62 return 0; 63 } else if (this.progress > MAX_PROGRESS) { 64 return MAX_PROGRESS; 65 } 66 return this.progress; 67 } 68 69 @Monitor('progress') 70 private getProgressContext() { 71 if (this.progress < 0) { 72 this.isLoading = false; 73 this.textProgress = MIN_PERCENTAGE; 74 } else if (this.progress >= MAX_PROGRESS) { 75 this.isLoading = false; 76 this.textProgress = MAX_PERCENTAGE; 77 } else { 78 this.isLoading = true; 79 this.textProgress = `${Math.floor(this.progress / MAX_PROGRESS * MAX_PROGRESS)}%`; 80 } 81 } 82 83 @Monitor('isLoading') 84 private getLoadingProgress() { 85 if (this.isLoading) { 86 if (this.progress < 0) { 87 this.textProgress = MIN_PERCENTAGE; 88 } else if (this.progress >= MAX_PROGRESS) { 89 this.textProgress = MAX_PERCENTAGE; 90 } else { 91 this.textProgress = `${Math.floor(this.progress / MAX_PROGRESS * MAX_PROGRESS)}%`; 92 } 93 } 94 } 95 96 private getProgressButtonRadius(): LengthMetrics { 97 if (!this.progressButtonRadius || this.progressButtonRadius.unit === LengthUnit.PERCENT) { 98 return LengthMetrics.vp(this.buttonBorderRadius); 99 } else if (this.progressButtonRadius.value < 0) { 100 return LengthMetrics.vp(0); 101 } else { 102 return this.progressButtonRadius; 103 } 104 } 105 106 build() { 107 Button() { 108 Stack() { 109 Progress({ 110 value: this.getButtonProgress(), total: MAX_PROGRESS, 111 style: ProgressStyle.Capsule 112 }) 113 .height(this.textHeight) 114 .constraintSize({ minHeight: BUTTON_NORMARL_HEIGHT }) 115 .borderRadius(this.buttonBorderRadius) 116 .width('100%') 117 .hoverEffect(HoverEffect.None) 118 .style({ 119 borderColor: this.colorOptions?.borderColor?.color ? 120 this.colorOptions?.borderColor?.color : this.containerBorderColor, 121 borderRadius: this.getProgressButtonRadius() 122 }) 123 .clip(false) 124 .enabled(this.isEnabled) 125 .key(PROGRESS_BUTTON_PROGRESS_KEY) 126 .color(this.colorOptions?.progressColor?.color ? this.colorOptions?.progressColor?.color : this.progressColor) 127 Row() { 128 Text(this.isLoading ? this.textProgress : this.content) 129 .fontSize($r('sys.float.ohos_id_text_size_button3')) 130 .fontWeight(FontWeight.Medium) 131 .key(PROGRESS_BUTTON_PRIMARY_FONT_KEY) 132 .fontColor(this.colorOptions?.textColor?.color) 133 .maxLines(1) 134 .textOverflow({ overflow: TextOverflow.Ellipsis }) 135 .padding({ 136 top: 4, 137 left: 8, 138 right: 8, 139 bottom: 4 140 }) 141 .opacity(this.isEnabled ? TEXT_ENABLE : TEXT_OPACITY) 142 .onSizeChange((_, newValue) => { 143 if (!newValue.height || newValue.height === this.textHeight) { 144 return; 145 } 146 this.textHeight = newValue.height > BUTTON_NORMARL_HEIGHT ? newValue.height : BUTTON_NORMARL_HEIGHT; 147 this.buttonBorderRadius = Number(this.textHeight) / 2; 148 }) 149 } 150 .constraintSize({ minHeight: BUTTON_NORMARL_HEIGHT }) 151 152 Row() 153 .key(PROGRESS_BUTTON_CONTAINER_BACKGROUND_COLOR_KEY) 154 .backgroundColor(Color.Transparent) 155 .border({ 156 width: 1, 157 color: this.colorOptions?.borderColor?.color ? 158 this.colorOptions?.borderColor?.color : this.containerBorderColor 159 }) 160 .height(this.textHeight) 161 .constraintSize({ minHeight: BUTTON_NORMARL_HEIGHT }) 162 .borderRadius(this.progressButtonRadius ? this.toLengthString(this.getProgressButtonRadius()) : 163 this.buttonBorderRadius) 164 .width('100%') 165 } 166 } 167 .borderRadius(this.progressButtonRadius ? this.toLengthString(this.getProgressButtonRadius()) : 168 this.buttonBorderRadius) 169 .clip(false) 170 .hoverEffect(HoverEffect.None) 171 .key(PROGRESS_BUTTON_EMPHASIZE_SECONDARY_BUTTON_KEY) 172 .backgroundColor( 173 this.colorOptions?.backgroundColor?.color 174 ? this.colorOptions?.backgroundColor?.color 175 : this.containerBackgroundColor) 176 .padding({ top: 0, bottom: 0 }) 177 .width((!this.progressButtonWidth || this.progressButtonWidth.value < 0) ? 178 BUTTON_NORMARL_WIDTH : this.toLengthString(this.progressButtonWidth)) 179 .constraintSize({ minWidth: BUTTON_NORMARL_WIDTH }) 180 .stateEffect(this.isEnabled) 181 .onClick(() => { 182 if (!this.isEnabled) { 183 return; 184 } 185 if (this.progress < MAX_PROGRESS) { 186 this.isLoading = !this.isLoading; 187 } 188 this.onClicked?.(); 189 }) 190 } 191 192 private toLengthString(value: LengthMetrics | undefined): string { 193 if (value === void (0)) { 194 return ''; 195 } 196 const length: number = value.value; 197 switch (value.unit) { 198 case LengthUnit.PX: 199 return `${length}px`; 200 case LengthUnit.FP: 201 return `${length}fp`; 202 case LengthUnit.LPX: 203 return `${length}lpx`; 204 case LengthUnit.PERCENT: 205 return `${length * 100}%`; 206 case LengthUnit.VP: 207 return `${length}vp`; 208 default: 209 return `${length}vp`; 210 } 211 } 212} 213 214@ObservedV2 215export class ProgressButtonV2Color { 216 @Trace public progressColor?: ColorMetrics; 217 @Trace public borderColor?: ColorMetrics; 218 @Trace public textColor?: ColorMetrics; 219 @Trace public backgroundColor?: ColorMetrics; 220 221 constructor(options: ProgressButtonV2ColorOptions) { 222 this.progressColor = options.progressColor; 223 this.borderColor = options.borderColor; 224 this.textColor = options.textColor; 225 this.backgroundColor = options.backgroundColor; 226 } 227} 228 229export type ClickCallback = () => void; 230 231export interface ProgressButtonV2ColorOptions { 232 progressColor?: ColorMetrics; 233 borderColor?: ColorMetrics; 234 textColor?: ColorMetrics; 235 backgroundColor?: ColorMetrics; 236}