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_slider.h"
17
18#include "common/image.h"
19#include "dock/focus_manager.h"
20#include "dock/vibrator_manager.h"
21#include "draw/draw_image.h"
22#include "engines/gfx/gfx_engine_manager.h"
23#include "gfx_utils/graphic_log.h"
24#include "imgdecode/cache_manager.h"
25#include "themes/theme_manager.h"
26
27namespace OHOS {
28UISlider::UISlider() : knobWidth_(0), knobStyleAllocFlag_(false), knobImage_(nullptr), listener_(nullptr)
29{
30    touchable_ = true;
31    draggable_ = true;
32    dragParentInstead_ = false;
33#if ENABLE_FOCUS_MANAGER
34    focusable_ = true;
35#endif
36#if ENABLE_ROTATE_INPUT
37    rotateFactor_ = DEFAULT_SLIDER_ROTATE_FACTOR;
38    cachedRotation_ = 0;
39#endif
40
41    Theme* theme = ThemeManager::GetInstance().GetCurrent();
42    if (theme != nullptr) {
43        knobStyle_ = &(theme->GetSliderKnobStyle());
44    } else {
45        knobStyle_ = &(StyleDefault::GetSliderKnobStyle());
46    }
47}
48
49UISlider::~UISlider()
50{
51    if (knobImage_ != nullptr) {
52        delete knobImage_;
53        knobImage_ = nullptr;
54    }
55
56    if (knobStyleAllocFlag_) {
57        delete knobStyle_;
58        knobStyle_ = nullptr;
59        knobStyleAllocFlag_ = false;
60    }
61}
62
63void UISlider::SetKnobStyle(const Style& style)
64{
65    if (!knobStyleAllocFlag_) {
66        knobStyle_ = new Style;
67        if (knobStyle_ == nullptr) {
68            GRAPHIC_LOGE("new Style fail");
69            return;
70        }
71        knobStyleAllocFlag_ = true;
72    }
73    *knobStyle_ = style;
74}
75
76void UISlider::SetKnobStyle(uint8_t key, int64_t value)
77{
78    if (!knobStyleAllocFlag_) {
79        knobStyle_ = new Style(*knobStyle_);
80        if (knobStyle_ == nullptr) {
81            GRAPHIC_LOGE("new Style fail");
82            return;
83        }
84        knobStyleAllocFlag_ = true;
85    }
86    knobStyle_->SetStyle(key, value);
87}
88
89const Style& UISlider::GetKnobStyle() const
90{
91    return *knobStyle_;
92}
93
94int64_t UISlider::GetKnobStyle(uint8_t key) const
95{
96    return knobStyle_->GetStyle(key);
97}
98
99void UISlider::SetKnobImage(const ImageInfo* knobImage)
100{
101    if (!InitImage()) {
102        return;
103    }
104    knobImage_->SetSrc(knobImage);
105}
106
107void UISlider::SetKnobImage(const char* knobImage)
108{
109    if (!InitImage()) {
110        return;
111    }
112    knobImage_->SetSrc(knobImage);
113}
114
115void UISlider::DrawKnob(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea, const Rect& foregroundRect)
116{
117    int16_t halfKnobWidth = GetKnobWidth() / 2; // 2: half
118    int16_t offset;
119    Rect knobBar;
120    switch (direction_) {
121        case Direction::DIR_LEFT_TO_RIGHT: {
122            offset = (knobWidth_ - progressHeight_) / 2; // 2: half
123            knobBar.SetRect(foregroundRect.GetRight() - halfKnobWidth, foregroundRect.GetTop() - offset,
124                            foregroundRect.GetRight() + halfKnobWidth, foregroundRect.GetBottom() + offset);
125            break;
126        }
127        case Direction::DIR_RIGHT_TO_LEFT: {
128            offset = (knobWidth_ - progressHeight_) / 2; // 2: half
129            knobBar.SetRect(foregroundRect.GetLeft() - halfKnobWidth, foregroundRect.GetTop() - offset,
130                            foregroundRect.GetLeft() + halfKnobWidth, foregroundRect.GetBottom() + offset);
131            break;
132        }
133        case Direction::DIR_BOTTOM_TO_TOP: {
134            offset = (knobWidth_ - progressWidth_) / 2; // 2: half
135            knobBar.SetRect(foregroundRect.GetLeft() - offset, foregroundRect.GetTop() - halfKnobWidth,
136                            foregroundRect.GetRight() + offset, foregroundRect.GetTop() + halfKnobWidth);
137            break;
138        }
139        case Direction::DIR_TOP_TO_BOTTOM: {
140            offset = (knobWidth_ - progressWidth_) / 2; // 2: half
141            knobBar.SetRect(foregroundRect.GetLeft() - offset, foregroundRect.GetBottom() - halfKnobWidth,
142                            foregroundRect.GetRight() + offset, foregroundRect.GetBottom() + halfKnobWidth);
143            break;
144        }
145        default: {
146            GRAPHIC_LOGW("UISlider::DrawKnob Direction error!\n");
147        }
148    }
149    DrawValidRect(gfxDstBuffer, knobImage_, knobBar, invalidatedArea, *knobStyle_, 0);
150}
151
152bool UISlider::InitImage()
153{
154    if (!UIAbstractProgress::InitImage()) {
155        return false;
156    }
157    if (knobImage_ == nullptr) {
158        knobImage_ = new Image();
159        if (knobImage_ == nullptr) {
160            GRAPHIC_LOGE("new Image fail");
161            return false;
162        }
163    }
164    return true;
165}
166
167void UISlider::SetImage(const ImageInfo* backgroundImage, const ImageInfo* foregroundImage)
168{
169    if (!InitImage()) {
170        return;
171    }
172    backgroundImage_->SetSrc(backgroundImage);
173    foregroundImage_->SetSrc(foregroundImage);
174}
175
176void UISlider::SetImage(const char* backgroundImage, const char* foregroundImage)
177{
178    if (!InitImage()) {
179        return;
180    }
181    backgroundImage_->SetSrc(backgroundImage);
182    foregroundImage_->SetSrc(foregroundImage);
183}
184
185void UISlider::DrawForeground(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea, Rect& coords)
186{
187    Point startPoint;
188    int16_t progressWidth;
189    int16_t progressHeight;
190    uint16_t radius;
191    GetBackgroundParam(startPoint, progressWidth, progressHeight, radius, *foregroundStyle_);
192
193    int16_t left;
194    int16_t right;
195    int16_t top;
196    int16_t bottom;
197    int16_t length;
198    Rect foregroundRect;
199
200    switch (direction_) {
201        case Direction::DIR_LEFT_TO_RIGHT: {
202            length = GetCurrentPos(progressWidth_ + 1);
203            foregroundRect.SetRect(startPoint.x, startPoint.y, startPoint.x + progressWidth - 1,
204                                   startPoint.y + progressHeight_ - 1);
205
206            left = startPoint.x - radius - 1;
207            right = left + length;
208            coords.SetRect(left, startPoint.y, right, startPoint.y + progressHeight_ - 1);
209            break;
210        }
211        case Direction::DIR_RIGHT_TO_LEFT: {
212            length = GetCurrentPos(progressWidth_ + 1);
213            foregroundRect.SetRect(startPoint.x, startPoint.y, startPoint.x + progressWidth - 1,
214                                   startPoint.y + progressHeight_ - 1);
215
216            right = startPoint.x + progressWidth + radius + 1;
217            left = right - length;
218            coords.SetRect(left, startPoint.y, right, startPoint.y + progressHeight_ - 1);
219            break;
220        }
221        case Direction::DIR_TOP_TO_BOTTOM: {
222            length = GetCurrentPos(progressHeight_ + 1);
223            foregroundRect.SetRect(startPoint.x, startPoint.y, startPoint.x + progressWidth_ - 1,
224                                   startPoint.y + progressHeight - 1);
225
226            top = startPoint.y - radius - 1;
227            bottom = top + length;
228            coords.SetRect(startPoint.x, top, startPoint.x + progressWidth_ - 1, bottom);
229            break;
230        }
231        case Direction::DIR_BOTTOM_TO_TOP: {
232            length = GetCurrentPos(progressHeight_ + 1);
233            foregroundRect.SetRect(startPoint.x, startPoint.y, startPoint.x + progressWidth_ - 1,
234                                   startPoint.y + progressHeight - 1);
235
236            bottom = startPoint.y + progressHeight + radius + 1;
237            top = bottom - length;
238            coords.SetRect(startPoint.x, top, startPoint.x + progressWidth_ - 1, bottom);
239            break;
240        }
241        default: {
242            GRAPHIC_LOGE("UISlider: DrawForeground direction Err!\n");
243            return;
244        }
245    }
246    if (coords.Intersect(coords, invalidatedArea)) {
247        DrawValidRect(gfxDstBuffer, foregroundImage_, foregroundRect, coords, *foregroundStyle_, radius);
248    }
249}
250
251void UISlider::OnDraw(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea)
252{
253    BaseGfxEngine::GetInstance()->DrawRect(gfxDstBuffer, GetOrigRect(), invalidatedArea, *style_, opaScale_);
254
255    Rect trunc(invalidatedArea);
256    if (trunc.Intersect(trunc, GetOrigRect())) {
257        DrawBackground(gfxDstBuffer, trunc);
258        Rect foregroundRect;
259        DrawForeground(gfxDstBuffer, trunc, foregroundRect);
260        DrawKnob(gfxDstBuffer, trunc, foregroundRect);
261    }
262}
263
264int32_t UISlider::CalculateCurrentValue(int16_t length, int16_t totalLength)
265{
266    if (totalLength != 0) {
267        return static_cast<int32_t>(rangeMin_ + (static_cast<int64_t>(rangeMax_) - rangeMin_) * length / totalLength);
268    }
269    return 0;
270}
271
272int32_t UISlider::UpdateCurrentValue(const Point& knobPosition)
273{
274    Point startPoint;
275    Rect rect = GetOrigRect();
276    // 2: Half of the gap
277    startPoint.x = rect.GetLeft() + style_->borderWidth_ + style_->paddingLeft_ + (GetWidth() - progressWidth_) / 2;
278    // 2: Half of the gap
279    startPoint.y = rect.GetTop() + style_->borderWidth_ + style_->paddingTop_ + (GetHeight() - progressHeight_) / 2;
280
281    int32_t value = curValue_;
282    switch (direction_) {
283        case Direction::DIR_LEFT_TO_RIGHT:
284            if (knobPosition.x <= startPoint.x) {
285                value = rangeMin_;
286            } else if (knobPosition.x >= startPoint.x + progressWidth_) {
287                value = rangeMax_;
288            } else {
289                value = CalculateCurrentValue(knobPosition.x - startPoint.x, progressWidth_);
290            }
291            break;
292        case Direction::DIR_RIGHT_TO_LEFT:
293            if (knobPosition.x <= startPoint.x) {
294                value = rangeMax_;
295            } else if (knobPosition.x >= startPoint.x + progressWidth_) {
296                value = rangeMin_;
297            } else {
298                value = CalculateCurrentValue(startPoint.x + progressWidth_ - knobPosition.x, progressWidth_);
299            }
300            break;
301        case Direction::DIR_BOTTOM_TO_TOP:
302            if (knobPosition.y <= startPoint.y) {
303                value = rangeMax_;
304            } else if (knobPosition.y >= startPoint.y + progressHeight_) {
305                value = rangeMin_;
306            } else {
307                value = CalculateCurrentValue(startPoint.y + progressHeight_ - knobPosition.y, progressHeight_);
308            }
309            break;
310        case Direction::DIR_TOP_TO_BOTTOM:
311            if (knobPosition.y <= startPoint.y) {
312                value = rangeMin_;
313            } else if (knobPosition.y >= startPoint.y + progressHeight_) {
314                value = rangeMax_;
315            } else {
316                value = CalculateCurrentValue(knobPosition.y - startPoint.y, progressHeight_);
317            }
318            break;
319        default:
320            GRAPHIC_LOGW("UISlider::UpdateCurrentValue Direction error!\n");
321    }
322    SetValue(value);
323    return value;
324}
325
326bool UISlider::OnClickEvent(const ClickEvent& event)
327{
328    Point knobPosition = event.GetCurrentPos();
329    int32_t value = UpdateCurrentValue(knobPosition);
330    if (listener_ != nullptr) {
331        listener_->OnChange(value);
332    }
333    bool ret = UIView::OnClickEvent(event);
334    Invalidate();
335    return ret;
336}
337
338bool UISlider::OnDragEvent(const DragEvent& event)
339{
340    Point knobPosition = event.GetCurrentPos();
341    int32_t value = UpdateCurrentValue(knobPosition);
342    if (listener_ != nullptr) {
343        listener_->OnChange(value);
344    }
345    Invalidate();
346    return UIView::OnDragEvent(event);
347}
348
349bool UISlider::OnDragEndEvent(const DragEvent& event)
350{
351    Point knobPosition = event.GetCurrentPos();
352    int32_t value = UpdateCurrentValue(knobPosition);
353    if (listener_ != nullptr) {
354        listener_->OnChange(value);
355        listener_->OnRelease(value);
356    }
357    Invalidate();
358    return UIView::OnDragEndEvent(event);
359}
360
361#if ENABLE_ROTATE_INPUT
362bool UISlider::OnRotateEvent(const RotateEvent& event)
363{
364    int32_t realRotation = 0;
365    cachedRotation_ += event.GetRotate() * rotateFactor_;
366    realRotation = static_cast<int32_t>(cachedRotation_);
367    if (realRotation == 0) {
368        return UIView::OnRotateEvent(event);
369    }
370    cachedRotation_ = 0;
371#if ENABLE_VIBRATOR
372    int32_t lastValue = curValue_;
373#endif
374    SetValue(curValue_ + realRotation);
375    if (listener_ != nullptr) {
376        listener_->OnChange(curValue_);
377    }
378#if ENABLE_VIBRATOR
379    VibratorFunc vibratorFunc = VibratorManager::GetInstance()->GetVibratorFunc();
380    if (vibratorFunc != nullptr && lastValue != curValue_) {
381        if (curValue_ == rangeMin_ || curValue_ == rangeMax_) {
382            GRAPHIC_LOGI("UISlider::OnRotateEvent calls TYPE_THREE vibrator");
383            vibratorFunc(VibratorType::VIBRATOR_TYPE_THREE);
384        } else {
385            int32_t changedValue = MATH_ABS(curValue_ - lastValue);
386            for (int32_t i = 0; i < changedValue; i++) {
387                GRAPHIC_LOGI("UISlider::OnRotateEvent calls TYPE_TWO vibrator");
388                vibratorFunc(VibratorType::VIBRATOR_TYPE_TWO);
389            }
390        }
391    }
392#endif
393    return UIView::OnRotateEvent(event);
394}
395
396bool UISlider::OnRotateEndEvent(const RotateEvent& event)
397{
398    cachedRotation_ = 0;
399    return UIView::OnRotateEndEvent(event);
400}
401#endif
402} // namespace OHOS
403