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