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_analog_clock.h"
17
18#include "components/ui_image_view.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.h"
25
26namespace OHOS {
27UIAnalogClock::UIAnalogClock()
28{
29    touchable_ = true;
30}
31
32void UIAnalogClock::SetHandImage(HandType type, const UIImageView& img, Point position, Point center)
33{
34    Hand* hand = nullptr;
35    if (type == HandType::HOUR_HAND) {
36        hand = &hourHand_;
37    } else if (type == HandType::MINUTE_HAND) {
38        hand = &minuteHand_;
39    } else {
40        hand = &secondHand_;
41    }
42
43    hand->center_ = center;
44    hand->position_ = position;
45    hand->initAngle_ = 0;
46    hand->preAngle_ = 0;
47    hand->nextAngle_ = 0;
48    hand->drawtype_ = DrawType::DRAW_IMAGE;
49
50    if (img.GetSrcType() == IMG_SRC_FILE) {
51        CacheEntry entry;
52        RetCode ret = CacheManager::GetInstance().Open(img.GetPath(), *style_, entry);
53        if (ret != RetCode::OK) {
54            return;
55        }
56        hand->imageInfo_ = entry.GetImageInfo();
57    } else {
58        if (img.GetImageInfo() == nullptr) {
59            hand->imageInfo_.data = nullptr;
60            return;
61        }
62        hand->imageInfo_ = *(img.GetImageInfo());
63    }
64}
65
66void UIAnalogClock::SetHandLine(HandType type,
67                                Point position,
68                                Point center,
69                                ColorType color,
70                                uint16_t width,
71                                uint16_t height,
72                                OpacityType opacity)
73{
74    Hand* hand = nullptr;
75    if (type == HandType::HOUR_HAND) {
76        hand = &hourHand_;
77    } else if (type == HandType::MINUTE_HAND) {
78        hand = &minuteHand_;
79    } else {
80        hand = &secondHand_;
81    }
82
83    hand->color_ = color;
84    hand->height_ = height;
85    hand->width_ = width;
86    hand->position_ = position;
87    hand->center_ = center;
88    hand->opacity_ = opacity;
89    hand->initAngle_ = 0;
90    hand->preAngle_ = 0;
91    hand->nextAngle_ = 0;
92    hand->drawtype_ = DrawType::DRAW_LINE;
93}
94
95Point UIAnalogClock::GetHandRotateCenter(HandType type) const
96{
97    if (type == HandType::HOUR_HAND) {
98        return hourHand_.center_;
99    } else if (type == HandType::MINUTE_HAND) {
100        return minuteHand_.center_;
101    } else {
102        return secondHand_.center_;
103    }
104}
105
106Point UIAnalogClock::GetHandPosition(HandType type) const
107{
108    if (type == HandType::HOUR_HAND) {
109        return hourHand_.position_;
110    } else if (type == HandType::MINUTE_HAND) {
111        return minuteHand_.position_;
112    } else {
113        return secondHand_.position_;
114    }
115}
116
117uint16_t UIAnalogClock::GetHandInitAngle(HandType type) const
118{
119    if (type == HandType::HOUR_HAND) {
120        return hourHand_.initAngle_;
121    } else if (type == HandType::MINUTE_HAND) {
122        return minuteHand_.initAngle_;
123    } else {
124        return secondHand_.initAngle_;
125    }
126}
127
128uint16_t UIAnalogClock::GetHandCurrentAngle(HandType type) const
129{
130    if (type == HandType::HOUR_HAND) {
131        return hourHand_.nextAngle_;
132    } else if (type == HandType::MINUTE_HAND) {
133        return minuteHand_.nextAngle_;
134    } else {
135        return secondHand_.nextAngle_;
136    }
137}
138
139void UIAnalogClock::SetInitTime24Hour(uint8_t hour, uint8_t minute, uint8_t second)
140{
141    currentHour_ = hour % ONE_DAY_IN_HOUR;
142    currentMinute_ = minute % ONE_HOUR_IN_MINUTE;
143    currentSecond_ = second % ONE_MINUTE_IN_SECOND;
144
145    hourHand_.initAngle_ = ConvertHandValueToAngle(currentHour_, HALF_DAY_IN_HOUR, currentMinute_, ONE_HOUR_IN_MINUTE);
146    hourHand_.preAngle_ = hourHand_.initAngle_;
147    hourHand_.nextAngle_ = hourHand_.initAngle_;
148
149    minuteHand_.initAngle_ =
150        ConvertHandValueToAngle(currentMinute_, ONE_HOUR_IN_MINUTE, currentSecond_, ONE_MINUTE_IN_SECOND);
151    minuteHand_.preAngle_ = minuteHand_.initAngle_;
152    minuteHand_.nextAngle_ = minuteHand_.initAngle_;
153
154    secondHand_.initAngle_ = ConvertHandValueToAngle(currentSecond_, ONE_MINUTE_IN_SECOND);
155    secondHand_.preAngle_ = secondHand_.initAngle_;
156    secondHand_.nextAngle_ = secondHand_.initAngle_;
157
158    UpdateClock(true);
159    Invalidate();
160}
161
162void UIAnalogClock::SetInitTime12Hour(uint8_t hour, uint8_t minute, uint8_t second, bool am)
163{
164    SetInitTime24Hour((hour % HALF_DAY_IN_HOUR) + (am ? 0 : HALF_DAY_IN_HOUR), minute, second);
165}
166
167uint16_t UIAnalogClock::ConvertHandValueToAngle(uint8_t handValue,
168                                                uint8_t range,
169                                                uint8_t secondHandValue,
170                                                uint8_t ratio) const
171{
172    if ((range == 0) || (ratio == 0)) {
173        GRAPHIC_LOGW("UIAnalogClock::ConvertHandValueToAngle Invalid range or ratio\n");
174        return 0;
175    }
176    /*
177     * Example: calculate the angle of hour hand
178     * Assume that the time is 5: 30, then range is 12, radio is 60
179     * angle is [(5 * 60  + 30) / (12 * 60)] * 360
180     */
181    uint32_t degree = (static_cast<uint16_t>(handValue) * ratio + secondHandValue);
182    degree = static_cast<uint32_t>(CIRCLE_IN_DEGREE * degree / (static_cast<uint16_t>(range) * ratio));
183
184    return static_cast<uint16_t>(degree % CIRCLE_IN_DEGREE);
185}
186
187uint16_t UIAnalogClock::ConvertHandValueToAngle(uint8_t handValue, uint8_t range) const
188{
189    if (range == 0) {
190        GRAPHIC_LOGW("UIAnalogClock::ConvertHandValueToAngle Invalid range or ratio\n");
191        return 0;
192    }
193    /*
194     * Example: calculate the angle of second hand without millisecond handle
195     * Assume that the time is 5:30:30, then range is 60
196     * angle is (30 / 60) * 360
197     */
198    return (static_cast<uint16_t>(handValue) * CIRCLE_IN_DEGREE / range);
199}
200
201void UIAnalogClock::UpdateClock(bool clockInit)
202{
203    hourHand_.nextAngle_ = ConvertHandValueToAngle(currentHour_, HALF_DAY_IN_HOUR, currentMinute_, ONE_HOUR_IN_MINUTE);
204    minuteHand_.nextAngle_ =
205        ConvertHandValueToAngle(currentMinute_, ONE_HOUR_IN_MINUTE, currentSecond_, ONE_MINUTE_IN_SECOND);
206    secondHand_.nextAngle_ = ConvertHandValueToAngle(currentSecond_, ONE_MINUTE_IN_SECOND);
207
208    Rect rect = GetRect();
209    CalculateRedrawArea(rect, hourHand_, clockInit);
210    CalculateRedrawArea(rect, minuteHand_, clockInit);
211    if (GetWorkMode() == WorkMode::NORMAL) {
212        CalculateRedrawArea(rect, secondHand_, clockInit);
213    }
214}
215
216void UIAnalogClock::OnDraw(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea)
217{
218    BaseGfxEngine::GetInstance()->DrawRect(gfxDstBuffer, GetRect(), invalidatedArea, *style_, opaScale_);
219}
220
221void UIAnalogClock::OnPostDraw(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea)
222{
223    UpdateClock(true);
224    Rect current = GetOrigRect();
225    DrawHand(gfxDstBuffer, current, invalidatedArea, hourHand_);
226    DrawHand(gfxDstBuffer, current, invalidatedArea, minuteHand_);
227    if (GetWorkMode() == WorkMode::NORMAL) {
228        DrawHand(gfxDstBuffer, current, invalidatedArea, secondHand_);
229    }
230    UIView::OnPostDraw(gfxDstBuffer, invalidatedArea);
231}
232
233void UIAnalogClock::CalculateRedrawArea(const Rect& current, Hand& hand, bool clockInit)
234{
235    /*
236     * Use the current image as an independent rectangular area
237     * to calculate the coordinate conversion coefficient.
238     */
239    int16_t imgWidth = hand.imageInfo_.header.width;
240    int16_t imgHeight = hand.imageInfo_.header.height;
241
242    int16_t left = hand.position_.x + current.GetLeft();
243    int16_t right = left + imgWidth - 1;
244    int16_t top = hand.position_.y + current.GetTop();
245    int16_t bottom = top + imgHeight - 1;
246    Rect imgRect(left, top, right, bottom);
247    TransformMap backwardMap(imgRect);
248    Vector2<float> pivot;
249    pivot.x_ = hand.center_.x;
250    pivot.y_ = hand.center_.y;
251
252    /* Rotate the specified angle,  */
253    backwardMap.Rotate(hand.nextAngle_ - hand.initAngle_, pivot);
254    Rect redraw = hand.target_;
255    hand.target_ = backwardMap.GetBoxRect();
256    hand.trans_ = backwardMap;
257    hand.preAngle_ = hand.nextAngle_;
258    if (!clockInit) {
259        /* Prevent old images from being residued */
260        redraw.Join(redraw, hand.target_);
261        InvalidateRect(redraw);
262    }
263}
264
265void UIAnalogClock::DrawHand(BufferInfo& gfxDstBuffer, const Rect& current, const Rect& invalidatedArea, Hand& hand)
266{
267    if (hand.drawtype_ == DrawType::DRAW_IMAGE) {
268        DrawHandImage(gfxDstBuffer, current, invalidatedArea, hand);
269    } else {
270        DrawHandLine(gfxDstBuffer, invalidatedArea, hand);
271    }
272}
273
274void UIAnalogClock::DrawHandImage(BufferInfo& gfxDstBuffer,
275                                  const Rect& current,
276                                  const Rect& invalidatedArea,
277                                  Hand& hand)
278{
279    if (hand.imageInfo_.data == nullptr) {
280        return;
281    }
282    uint8_t pxSize = DrawUtils::GetPxSizeByColorMode(hand.imageInfo_.header.colorMode);
283    TransformDataInfo imageTranDataInfo = {hand.imageInfo_.header, hand.imageInfo_.data, pxSize, BlurLevel::LEVEL0,
284                                           TransformAlgorithm::BILINEAR};
285    BaseGfxEngine::GetInstance()->DrawTransform(gfxDstBuffer, invalidatedArea, {0, 0}, Color::Black(), opaScale_,
286                                                hand.trans_, imageTranDataInfo);
287}
288
289void UIAnalogClock::DrawHandLine(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea, Hand& hand)
290{
291    float sinma = Sin(hand.nextAngle_);
292    float cosma = Sin(hand.nextAngle_ + THREE_QUARTER_IN_DEGREE);
293    int32_t handLength = hand.height_;
294    Rect rect = GetRect();
295    Point start;
296    Point end;
297    Point curCenter;
298    curCenter.x = hand.position_.x + hand.center_.x + rect.GetLeft();
299    curCenter.y = hand.position_.y + hand.center_.y + rect.GetTop();
300
301    int32_t startToCenterLength = hand.center_.y;
302
303    int32_t xPointLength = static_cast<int32_t>(startToCenterLength * sinma);
304    int32_t yPointLength = static_cast<int32_t>(startToCenterLength * cosma);
305
306    start.x = xPointLength + curCenter.x;
307    start.y = yPointLength + curCenter.y;
308
309    /*
310     * @ startToCenterLength: means the length between StartPoint and CenterPoint.
311     * @ handlength: means the hand height.
312     * @ xlength: means X-axis length relative to the center point
313     * @ ylength: means Y-axis length relative to the center point
314     */
315    int32_t xlength = static_cast<int32_t>((startToCenterLength - handLength) * sinma);
316    int32_t ylength = static_cast<int32_t>((startToCenterLength - handLength) * cosma);
317    end.x = xlength + curCenter.x;
318    end.y = ylength + curCenter.y;
319
320    BaseGfxEngine::GetInstance()->DrawLine(gfxDstBuffer, start, end, invalidatedArea, hand.width_, hand.color_,
321                                           hand.opacity_);
322}
323
324void UIAnalogClock::SetWorkMode(WorkMode newMode)
325{
326    WorkMode oldMode = mode_;
327
328    if (oldMode != newMode) {
329        /*
330         * After entering the alwayson mode, all child controls are no longer drawn,
331         * making the simplest analog clock.
332         */
333        isViewGroup_ = (newMode == ALWAYS_ON) ? false : true;
334        mode_ = newMode;
335        Invalidate();
336    }
337}
338} // namespace OHOS
339