1/* 2 * Copyright (c) 2020-2021 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 16#include "components/ui_button.h" 17#include "animator/interpolation.h" 18#include "common/image.h" 19#include "draw/draw_image.h" 20#include "engines/gfx/gfx_engine_manager.h" 21#include "gfx_utils/graphic_log.h" 22#include "gfx_utils/style.h" 23#include "imgdecode/cache_manager.h" 24#include "themes/theme_manager.h" 25 26namespace OHOS { 27UIButton::UIButton() 28 : defaultImgSrc_(nullptr), 29 triggeredImgSrc_(nullptr), 30 currentImgSrc_(ButtonImageSrc::BTN_IMAGE_DEFAULT), 31 imgX_(0), 32 imgY_(0), 33 contentWidth_(0), 34 contentHeight_(0), 35 state_(RELEASED), 36 styleState_(RELEASED), 37#if DEFAULT_ANIMATION 38 enableAnimation_(true), 39 animator_(*this), 40#endif 41 buttonStyleAllocFlag_(false) 42{ 43 touchable_ = true; 44 SetupThemeStyles(); 45} 46 47UIButton::~UIButton() 48{ 49 if (defaultImgSrc_ != nullptr) { 50 delete defaultImgSrc_; 51 defaultImgSrc_ = nullptr; 52 } 53 54 if (triggeredImgSrc_ != nullptr) { 55 delete triggeredImgSrc_; 56 triggeredImgSrc_ = nullptr; 57 } 58 59 if (buttonStyleAllocFlag_) { 60 for (uint8_t i = 0; i < BTN_STATE_NUM; i++) { 61 delete buttonStyles_[i]; 62 buttonStyles_[i] = nullptr; 63 } 64 buttonStyleAllocFlag_ = false; 65 } 66} 67 68void UIButton::DrawImg(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea, OpacityType opaScale) 69{ 70 const Image* image = GetCurImageSrc(); 71 if (image == nullptr) { 72 return; 73 } 74 75 ImageHeader header = {0}; 76 image->GetHeader(header); 77 Rect coords; 78 Rect viewRect = GetContentRect(); 79 coords.SetLeft(viewRect.GetLeft() + GetImageX()); 80 coords.SetTop(viewRect.GetTop() + GetImageY()); 81 coords.SetWidth(header.width); 82 coords.SetHeight(header.height); 83 84 Rect trunc(invalidatedArea); 85 if (trunc.Intersect(trunc, viewRect)) { 86 image->DrawImage(gfxDstBuffer, coords, trunc, *buttonStyles_[state_], opaScale); 87 } 88} 89 90void UIButton::OnDraw(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea) 91{ 92 OpacityType opa = GetMixOpaScale(); 93 BaseGfxEngine::GetInstance()->DrawRect(gfxDstBuffer, GetOrigRect(), invalidatedArea, *buttonStyles_[state_], opa); 94 DrawImg(gfxDstBuffer, invalidatedArea, opa); 95} 96 97void UIButton::SetupThemeStyles() 98{ 99 Theme* theme = ThemeManager::GetInstance().GetCurrent(); 100 101 if (theme == nullptr) { 102 buttonStyles_[RELEASED] = &(StyleDefault::GetButtonReleasedStyle()); 103 buttonStyles_[PRESSED] = &(StyleDefault::GetButtonPressedStyle()); 104 buttonStyles_[INACTIVE] = &(StyleDefault::GetButtonInactiveStyle()); 105 } else { 106 buttonStyles_[RELEASED] = &(theme->GetButtonStyle().released); 107 buttonStyles_[PRESSED] = &(theme->GetButtonStyle().pressed); 108 buttonStyles_[INACTIVE] = &(theme->GetButtonStyle().inactive); 109 } 110 style_ = buttonStyles_[RELEASED]; 111} 112 113int64_t UIButton::GetStyle(uint8_t key) const 114{ 115 return GetStyleForState(key, styleState_); 116} 117 118void UIButton::SetStyle(uint8_t key, int64_t value) 119{ 120 SetStyleForState(key, value, styleState_); 121} 122 123int64_t UIButton::GetStyleForState(uint8_t key, ButtonState state) const 124{ 125 if (state < BTN_STATE_NUM) { 126 return (buttonStyles_[state])->GetStyle(key); 127 } 128 return 0; 129} 130 131void UIButton::SetStyleForState(uint8_t key, int64_t value, ButtonState state) 132{ 133 if (state < BTN_STATE_NUM) { 134 if (!buttonStyleAllocFlag_) { 135 for (uint8_t i = 0; i < BTN_STATE_NUM; i++) { 136 Style styleSaved = *buttonStyles_[i]; 137 buttonStyles_[i] = new Style; 138 if (buttonStyles_[i] == nullptr) { 139 GRAPHIC_LOGE("new Style fail"); 140 return; 141 } 142 *(buttonStyles_[i]) = styleSaved; 143 } 144 buttonStyleAllocFlag_ = true; 145 } 146 style_ = buttonStyles_[RELEASED]; 147 int16_t width = GetWidth(); 148 int16_t height = GetHeight(); 149 int16_t x = GetX(); 150 int16_t y = GetY(); 151 buttonStyles_[state]->SetStyle(key, value); 152 Rect rect(x, y, x + width - 1, y + height - 1); 153 UpdateRectInfo(key, rect); 154 } 155} 156 157bool UIButton::OnPressEvent(const PressEvent& event) 158{ 159 currentImgSrc_ = ButtonImageSrc::BTN_IMAGE_TRIGGERED; 160 SetState(PRESSED); 161 Resize(contentWidth_, contentHeight_); 162 Invalidate(); 163#if DEFAULT_ANIMATION 164 if (enableAnimation_) { 165 animator_.Start(); 166 } 167#endif 168 return UIView::OnPressEvent(event); 169} 170 171bool UIButton::OnReleaseEvent(const ReleaseEvent& event) 172{ 173 currentImgSrc_ = ButtonImageSrc::BTN_IMAGE_DEFAULT; 174 SetState(RELEASED); 175 Resize(contentWidth_, contentHeight_); 176 Invalidate(); 177#if DEFAULT_ANIMATION 178 if (enableAnimation_) { 179 animator_.Start(); 180 } 181#endif 182 return UIView::OnReleaseEvent(event); 183} 184 185bool UIButton::OnCancelEvent(const CancelEvent& event) 186{ 187 currentImgSrc_ = ButtonImageSrc::BTN_IMAGE_DEFAULT; 188 SetState(RELEASED); 189 Resize(contentWidth_, contentHeight_); 190 Invalidate(); 191#if DEFAULT_ANIMATION 192 if (enableAnimation_) { 193 animator_.Start(); 194 } 195#endif 196 return UIView::OnCancelEvent(event); 197} 198 199const Image* UIButton::GetCurImageSrc() const 200{ 201 if (currentImgSrc_ == ButtonImageSrc::BTN_IMAGE_DEFAULT) { 202 return defaultImgSrc_; 203 } else if (currentImgSrc_ == ButtonImageSrc::BTN_IMAGE_TRIGGERED) { 204 return triggeredImgSrc_; 205 } else { 206 return nullptr; 207 } 208} 209 210void UIButton::SetImageSrc(const char* defaultImgSrc, const char* triggeredImgSrc) 211{ 212 if (!InitImage()) { 213 return; 214 } 215 defaultImgSrc_->SetSrc(defaultImgSrc); 216 triggeredImgSrc_->SetSrc(triggeredImgSrc); 217} 218 219void UIButton::SetImageSrc(const ImageInfo* defaultImgSrc, const ImageInfo* triggeredImgSrc) 220{ 221 if (!InitImage()) { 222 return; 223 } 224 defaultImgSrc_->SetSrc(defaultImgSrc); 225 triggeredImgSrc_->SetSrc(triggeredImgSrc); 226} 227 228void UIButton::Disable() 229{ 230 SetState(INACTIVE); 231 touchable_ = false; 232} 233 234void UIButton::Enable() 235{ 236 SetState(RELEASED); 237 touchable_ = true; 238} 239 240void UIButton::SetState(ButtonState state) 241{ 242 state_ = state; 243 style_ = buttonStyles_[state_]; 244 Invalidate(); 245} 246 247bool UIButton::InitImage() 248{ 249 if (defaultImgSrc_ == nullptr) { 250 defaultImgSrc_ = new Image(); 251 if (defaultImgSrc_ == nullptr) { 252 GRAPHIC_LOGE("new Image fail"); 253 return false; 254 } 255 } 256 if (triggeredImgSrc_ == nullptr) { 257 triggeredImgSrc_ = new Image(); 258 if (triggeredImgSrc_ == nullptr) { 259 GRAPHIC_LOGE("new Image fail"); 260 return false; 261 } 262 } 263 return true; 264} 265 266bool UIButton::OnPreDraw(Rect& invalidatedArea) const 267{ 268 Rect rect(GetRect()); 269 int16_t r = buttonStyles_[styleState_]->borderRadius_; 270 if (r == COORD_MAX) { 271 return true; 272 } 273 274 if (r != 0) { 275 r = ((r & 0x1) == 0) ? (r >> 1) : ((r + 1) >> 1); 276 rect.SetLeft(rect.GetX() + r); 277 rect.SetWidth(rect.GetWidth() - r); 278 rect.SetTop(rect.GetY() + r); 279 rect.SetHeight(rect.GetHeight() - r); 280 } 281 if (rect.IsContains(invalidatedArea)) { 282 return true; 283 } 284 invalidatedArea.Intersect(invalidatedArea, rect); 285 return false; 286} 287 288#if DEFAULT_ANIMATION 289void UIButton::OnPostDraw(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea) 290{ 291 if (state_ == ButtonState::PRESSED && enableAnimation_) { 292 animator_.DrawMask(gfxDstBuffer, invalidatedArea); 293 } 294 UIView::OnPostDraw(gfxDstBuffer, invalidatedArea); 295} 296 297namespace { 298constexpr float FULL_SCALE = 1.0f; 299constexpr float SHRINK_SCALE = 0.8f; 300constexpr uint32_t SHRINK_DURATION = 150; 301constexpr uint32_t RECOVER_DURATION = 200; 302constexpr int64_t MASK_OPA = 25; 303constexpr float BEZIER_CONTROL = 0.2f; 304} // namespace 305 306void UIButton::ButtonAnimator::Start() 307{ 308 bool isReverse = (button_.state_ == UIButton::ButtonState::PRESSED); 309 float targetScale = isReverse ? SHRINK_SCALE : FULL_SCALE; 310 if ((animator_.GetState() == Animator::STOP) && FloatEqual(targetScale, scale_)) { 311 return; 312 } 313 314 if (isReverse) { 315 animator_.SetTime(SHRINK_DURATION); 316 } else { 317 animator_.SetTime(RECOVER_DURATION); 318 } 319 animator_.Start(); 320 /* reverse the animator direction */ 321 float x = isReverseAnimation_ ? (FULL_SCALE - scale_) : (scale_ - SHRINK_SCALE); 322 float y = x / (FULL_SCALE - SHRINK_SCALE); 323 x = Interpolation::GetBezierY(FULL_SCALE - y, 0, BEZIER_CONTROL, FULL_SCALE, BEZIER_CONTROL); 324 animator_.SetRunTime(static_cast<uint32_t>(animator_.GetTime() * x)); 325 isReverseAnimation_ = isReverse; 326} 327 328void UIButton::ButtonAnimator::DrawMask(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea) 329{ 330 Style maskStyle; 331 maskStyle.SetStyle(STYLE_BACKGROUND_COLOR, Color::White().full); 332 maskStyle.SetStyle(STYLE_BACKGROUND_OPA, MASK_OPA); 333 maskStyle.SetStyle(STYLE_BORDER_RADIUS, button_.GetStyle(STYLE_BORDER_RADIUS)); 334 OpacityType opa = button_.GetMixOpaScale(); 335 BaseGfxEngine::GetInstance()->DrawRect(gfxDstBuffer, button_.GetRect(), invalidatedArea, maskStyle, opa); 336} 337 338static inline void ScaleButton(UIButton& button, float scale) 339{ 340 Vector2<float> scaleValue_ = {scale, scale}; 341 Vector2<float> centrePoint(button.GetWidth() / 2.0f, button.GetHeight() / 2.0f); 342 button.Scale(scaleValue_, centrePoint); 343} 344 345void UIButton::ButtonAnimator::Callback(UIView* view) 346{ 347 float x = static_cast<float>(animator_.GetRunTime()) / animator_.GetTime(); 348 float offset = Interpolation::GetBezierY(x, BEZIER_CONTROL, 0, BEZIER_CONTROL, FULL_SCALE); 349 float scale = (FULL_SCALE - SHRINK_SCALE) * offset; 350 351 scale_ = isReverseAnimation_ ? (FULL_SCALE - scale) : (scale + SHRINK_SCALE); 352 ScaleButton(button_, scale_); 353} 354 355void UIButton::ButtonAnimator::OnStop(UIView& view) 356{ 357 if (isReverseAnimation_) { 358 scale_ = SHRINK_SCALE; 359 ScaleButton(button_, SHRINK_SCALE); 360 } else { 361 scale_ = FULL_SCALE; 362 button_.ResetTransParameter(); 363 } 364} 365#endif 366} // namespace OHOS 367