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 
26 namespace OHOS {
UIButton()27 UIButton::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 
~UIButton()47 UIButton::~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 
DrawImg(BufferInfo & gfxDstBuffer,const Rect & invalidatedArea,OpacityType opaScale)68 void 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 
OnDraw(BufferInfo & gfxDstBuffer,const Rect & invalidatedArea)90 void 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 
SetupThemeStyles()97 void 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 
GetStyle(uint8_t key) const113 int64_t UIButton::GetStyle(uint8_t key) const
114 {
115     return GetStyleForState(key, styleState_);
116 }
117 
SetStyle(uint8_t key,int64_t value)118 void UIButton::SetStyle(uint8_t key, int64_t value)
119 {
120     SetStyleForState(key, value, styleState_);
121 }
122 
GetStyleForState(uint8_t key,ButtonState state) const123 int64_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 
SetStyleForState(uint8_t key,int64_t value,ButtonState state)131 void 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 
OnPressEvent(const PressEvent & event)157 bool 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 
OnReleaseEvent(const ReleaseEvent & event)171 bool 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 
OnCancelEvent(const CancelEvent & event)185 bool 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 
GetCurImageSrc() const199 const 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 
SetImageSrc(const char * defaultImgSrc,const char * triggeredImgSrc)210 void 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 
SetImageSrc(const ImageInfo * defaultImgSrc,const ImageInfo * triggeredImgSrc)219 void 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 
Disable()228 void UIButton::Disable()
229 {
230     SetState(INACTIVE);
231     touchable_ = false;
232 }
233 
Enable()234 void UIButton::Enable()
235 {
236     SetState(RELEASED);
237     touchable_ = true;
238 }
239 
SetState(ButtonState state)240 void UIButton::SetState(ButtonState state)
241 {
242     state_ = state;
243     style_ = buttonStyles_[state_];
244     Invalidate();
245 }
246 
InitImage()247 bool 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 
OnPreDraw(Rect & invalidatedArea) const266 bool 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
OnPostDraw(BufferInfo & gfxDstBuffer,const Rect & invalidatedArea)289 void 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 
297 namespace {
298 constexpr float FULL_SCALE = 1.0f;
299 constexpr float SHRINK_SCALE = 0.8f;
300 constexpr uint32_t SHRINK_DURATION = 150;
301 constexpr uint32_t RECOVER_DURATION = 200;
302 constexpr int64_t MASK_OPA = 25;
303 constexpr float BEZIER_CONTROL = 0.2f;
304 } // namespace
305 
Start()306 void 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 
DrawMask(BufferInfo & gfxDstBuffer,const Rect & invalidatedArea)328 void 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 
ScaleButton(UIButton & button,float scale)338 static 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 
Callback(UIView * view)345 void 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 
OnStop(UIView & view)355 void 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