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_arc_label.h"
17
18#include "common/typed_text.h"
19#include "draw/draw_label.h"
20#include "engines/gfx/gfx_engine_manager.h"
21#include "font/ui_font.h"
22#include "themes/theme_manager.h"
23
24namespace OHOS {
25static constexpr uint16_t DEFAULT_ARC_LABEL_ROLL_COUNT = 1;
26static constexpr uint16_t DEFAULT_ARC_LABEL_ANIMATOR_SPEED = 10;
27
28class ArcLabelAnimator : public Animator, public AnimatorCallback {
29public:
30    ArcLabelAnimator(uint16_t rollCount, UIView* view)
31        : Animator(this, view, 0, true),
32          waitCount_(ANIM_WAIT_COUNT),
33          speed_(0),
34          preRunTime_(0),
35          decimal_(0),
36          rollCount_(rollCount)
37    {
38    }
39
40    virtual ~ArcLabelAnimator() {}
41
42    void Callback(UIView* view) override
43    {
44        if (view == nullptr || rollCount_ == 0) {
45            return;
46        }
47
48        uint32_t curTime = GetRunTime();
49        if (waitCount_ > 0) {
50            waitCount_--;
51            preRunTime_ = curTime;
52            return;
53        }
54        if (curTime == preRunTime_) {
55            return;
56        }
57        uint32_t time = (curTime > preRunTime_) ? (curTime - preRunTime_) : (UINT32_MAX - preRunTime_ + curTime);
58        // 1000: 1000 milliseconds is 1 second
59        float floatStep = (static_cast<float>(time * speed_) / 1000) + decimal_;
60        uint16_t integerStep = static_cast<uint16_t>(floatStep);
61        decimal_ = floatStep - integerStep;
62        preRunTime_ = curTime;
63
64        if (integerStep == 0) {
65            return;
66        }
67
68        CalculatedOffsetAngle(view);
69    }
70
71    void SetRollSpeed(uint16_t speed)
72    {
73        speed_ = speed;
74    }
75
76    int16_t GetRollSpeed() const
77    {
78        return speed_;
79    }
80
81    void SetRollCount(uint16_t rollCount)
82    {
83        rollCount_ = rollCount;
84    }
85
86    void RegisterScrollListener(ArcLabelScrollListener* scrollListener)
87    {
88        scrollListener_ = scrollListener;
89    }
90
91private:
92    void CalculatedOffsetAngle(UIView* view)
93    {
94        if (view == nullptr) {
95            return;
96        }
97        UIArcLabel* arcLabel = static_cast<UIArcLabel*>(view);
98        if (arcLabel == nullptr) {
99            return;
100        }
101
102        int16_t startAngle = arcLabel->GetArcTextStartAngle();
103        int16_t endAngle = arcLabel->GetArcTextEndAngle();
104        uint16_t arcAngle = (startAngle < endAngle) ? (endAngle - startAngle) :
105            (startAngle - endAngle);
106
107        if (arcLabel->offsetAngle_ < arcAngle) {
108            arcLabel->offsetAngle_ += DEFAULT_CHANGE_ANGLE;
109        } else {
110            rollCount_--;
111            if (rollCount_ > 0) {
112                arcLabel->offsetAngle_ = arcLabel->animator_.secondLapOffsetAngle_;
113            }
114        }
115
116        if (rollCount_ == 0) {
117            if (scrollListener_) {
118                scrollListener_->Finish();
119            }
120            Stop();
121        }
122        view->Invalidate();
123    }
124
125private:
126    static constexpr uint8_t ANIM_WAIT_COUNT = 50;
127    static constexpr float DEFAULT_CHANGE_ANGLE = 1.0f;
128    uint16_t waitCount_;
129    uint16_t speed_;
130    uint32_t preRunTime_;
131    float decimal_;
132
133    uint16_t rollCount_;
134    ArcLabelScrollListener* scrollListener_;
135};
136
137UIArcLabel::UIArcLabel()
138    : arcLabelText_(nullptr),
139      compatibilityMode_(true),
140      offsetAngle_(0.0f),
141      arcTextInfo_{0},
142      needRefresh_(false),
143      hasAnimator_(false),
144      textSize_({0, 0}),
145      radius_(0),
146      startAngle_(0),
147      endAngle_(0),
148      arcCenter_({0, 0}),
149      orientation_(TextOrientation::INSIDE)
150{
151    Theme* theme = ThemeManager::GetInstance().GetCurrent();
152    style_ = (theme != nullptr) ? &(theme->GetLabelStyle()) : &(StyleDefault::GetLabelStyle());
153
154    animator_.animator = nullptr;
155    animator_.scrollListener = nullptr;
156    animator_.speed = DEFAULT_ARC_LABEL_ANIMATOR_SPEED;
157    animator_.rollCount = DEFAULT_ARC_LABEL_ROLL_COUNT;
158    animator_.secondLapOffsetAngle_ = 0.0f;
159}
160
161UIArcLabel::~UIArcLabel()
162{
163    if (arcLabelText_ != nullptr) {
164        delete arcLabelText_;
165        arcLabelText_ = nullptr;
166    }
167
168    if (hasAnimator_) {
169        delete animator_.animator;
170        animator_.animator = nullptr;
171        hasAnimator_ = false;
172    }
173}
174
175void UIArcLabel::SetStyle(uint8_t key, int64_t value)
176{
177    UIView::SetStyle(key, value);
178    RefreshArcLabel();
179}
180
181void UIArcLabel::SetText(const char* text)
182{
183    if (text == nullptr) {
184        return;
185    }
186    InitArcLabelText();
187    arcLabelText_->SetText(text);
188    if (arcLabelText_->IsNeedRefresh()) {
189        RefreshArcLabel();
190    }
191}
192
193const char* UIArcLabel::GetText() const
194{
195    return (arcLabelText_ == nullptr) ? nullptr : arcLabelText_->GetText();
196}
197
198void UIArcLabel::SetAlign(UITextLanguageAlignment horizontalAlign)
199{
200    InitArcLabelText();
201    arcLabelText_->SetAlign(horizontalAlign, TEXT_ALIGNMENT_TOP);
202    if (arcLabelText_->IsNeedRefresh()) {
203        RefreshArcLabel();
204    }
205}
206
207UITextLanguageAlignment UIArcLabel::GetHorAlign()
208{
209    InitArcLabelText();
210    return arcLabelText_->GetHorAlign();
211}
212
213UITextLanguageDirect UIArcLabel::GetDirect()
214{
215    InitArcLabelText();
216    return arcLabelText_->GetDirect();
217}
218
219void UIArcLabel::SetFontId(uint16_t fontId)
220{
221    InitArcLabelText();
222    arcLabelText_->SetFontId(fontId);
223    if (arcLabelText_->IsNeedRefresh()) {
224        RefreshArcLabel();
225    }
226}
227
228uint16_t UIArcLabel::GetFontId()
229{
230    InitArcLabelText();
231    return arcLabelText_->GetFontId();
232}
233
234void UIArcLabel::SetFont(const char* name, uint8_t size)
235{
236    if (name == nullptr) {
237        return;
238    }
239    InitArcLabelText();
240    arcLabelText_->SetFont(name, size);
241    if (arcLabelText_->IsNeedRefresh()) {
242        RefreshArcLabel();
243    }
244}
245
246void UIArcLabel::OnDraw(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea)
247{
248    InitArcLabelText();
249    const char* text = arcLabelText_->GetText();
250    if ((text == nullptr) || (radius_ == 0)) {
251        return;
252    }
253    OpacityType opa = GetMixOpaScale();
254    UIView::OnDraw(gfxDstBuffer, invalidatedArea);
255    DrawArcText(gfxDstBuffer, invalidatedArea, opa, arcTextInfo_, orientation_);
256}
257
258void UIArcLabel::DrawArcText(BufferInfo& gfxDstBuffer,
259                             const Rect& mask,
260                             OpacityType opaScale,
261                             ArcTextInfo arcTextInfo,
262                             TextOrientation orientation)
263{
264    Point center;
265    center.x = arcTextInfo_.arcCenter.x + GetRect().GetX();
266    center.y = arcTextInfo_.arcCenter.y + GetRect().GetY();
267    Rect temp = mask;
268    if (compatibilityMode_ && hasAnimator_) {
269        temp.SetLeft(center.x - radius_);
270        temp.SetTop(center.y - radius_);
271        temp.SetWidth(radius_ * 2); // 2 mean diameter
272        temp.SetHeight(radius_ * 2);
273    }
274    arcTextInfo.hasAnimator = hasAnimator_;
275
276    DrawLabel::DrawArcText(gfxDstBuffer, temp, arcLabelText_->GetText(), center, arcLabelText_->GetFontId(),
277                           arcLabelText_->GetFontSize(), arcTextInfo, offsetAngle_,
278                           orientation, *style_, opaScale, compatibilityMode_);
279}
280
281Rect UIArcLabel::GetArcTextRect(const char* text, uint16_t fontId, uint8_t fontSize, const Point& arcCenter,
282                                int16_t letterSpace, TextOrientation orientation, const ArcTextInfo& arcTextInfo)
283{
284    return TypedText::GetArcTextRect(text, fontId, fontSize, arcCenter, letterSpace, orientation, arcTextInfo);
285}
286
287void UIArcLabel::RefreshArcLabel()
288{
289    Invalidate();
290    if (!needRefresh_) {
291        needRefresh_ = true;
292    }
293}
294
295void UIArcLabel::ReMeasure()
296{
297    if (!needRefresh_) {
298        return;
299    }
300    needRefresh_ = false;
301    InitArcLabelText();
302
303    MeasureArcTextInfo();
304    arcTextInfo_.shapingFontId = arcLabelText_->GetShapingFontId();
305    arcTextInfo_.codePoints = arcLabelText_->GetCodePoints();
306    arcTextInfo_.codePointsNum = arcLabelText_->GetCodePointNum();
307    Rect textRect =
308        GetArcTextRect(arcLabelText_->GetText(), arcLabelText_->GetFontId(), arcLabelText_->GetFontSize(),
309                       arcCenter_, style_->letterSpace_, orientation_, arcTextInfo_);
310    int16_t arcTextWidth = textRect.GetWidth();
311    int16_t arcTextHeight = textRect.GetHeight();
312
313    if (compatibilityMode_) {
314        SetPosition(textRect.GetX(), textRect.GetY());
315        Resize(arcTextWidth, arcTextHeight);
316    }
317
318    arcTextInfo_.arcCenter.x = arcCenter_.x - GetX() + style_->borderWidth_ + style_->paddingLeft_;
319    arcTextInfo_.arcCenter.y = arcCenter_.y - GetY() + style_->borderWidth_ + style_->paddingTop_;
320    textSize_.x = arcTextWidth;
321    textSize_.y = arcTextHeight;
322    Invalidate();
323}
324
325uint32_t UIArcLabel::GetLineEnd(int16_t maxLength)
326{
327    const char* text = arcLabelText_->GetText();
328    if (text == nullptr) {
329        return 0;
330    }
331
332    return TypedText::GetNextLine(&text[arcTextInfo_.lineStart], arcLabelText_->GetFontId(),
333        arcLabelText_->GetFontSize(), style_->letterSpace_, maxLength);
334}
335
336void UIArcLabel::MeasureArcTextInfo()
337{
338    const char* text = arcLabelText_->GetText();
339    if (text == nullptr) {
340        return;
341    }
342    uint16_t letterHeight = UIFont::GetInstance()->GetHeight(arcLabelText_->GetFontId(), arcLabelText_->GetFontSize());
343    if (compatibilityMode_) {
344        arcTextInfo_.radius = ((orientation_ == TextOrientation::INSIDE) ? radius_ : (radius_ - letterHeight));
345    } else {
346        arcTextInfo_.radius = radius_;
347    }
348    if (arcTextInfo_.radius == 0) {
349        return;
350    }
351
352    uint16_t arcAngle;
353    if (startAngle_ < endAngle_) {
354        arcAngle = endAngle_ - startAngle_;
355        arcTextInfo_.direct = TEXT_DIRECT_LTR; // Clockwise
356        arcLabelText_->SetDirect(TEXT_DIRECT_LTR);
357    } else {
358        arcAngle = startAngle_ - endAngle_;
359        arcTextInfo_.direct = TEXT_DIRECT_RTL; // Counterclockwise
360        arcLabelText_->SetDirect(TEXT_DIRECT_RTL);
361    }
362
363    OnMeasureArcTextInfo(arcAngle, letterHeight);
364}
365
366void UIArcLabel::OnMeasureArcTextInfo(const uint16_t arcAngle, const uint16_t letterHeight)
367{
368    const char* text = arcLabelText_->GetText();
369    if (text == nullptr) {
370        return;
371    }
372
373    // calculate max arc length
374    float maxLength = static_cast<float>((UI_PI * radius_ * arcAngle) / SEMICIRCLE_IN_DEGREE);
375    arcTextInfo_.lineStart = 0;
376
377    Rect rect;
378    rect.SetWidth(static_cast<int16_t>(maxLength));
379    arcLabelText_->ReMeasureTextSize(rect, *style_);
380
381    arcTextInfo_.lineEnd = GetLineEnd(static_cast<int16_t>(maxLength));
382    arcTextInfo_.startAngle = startAngle_ > CIRCLE_IN_DEGREE ? startAngle_ % CIRCLE_IN_DEGREE : startAngle_;
383    arcTextInfo_.endAngle = endAngle_ > CIRCLE_IN_DEGREE ? endAngle_ % CIRCLE_IN_DEGREE : endAngle_;
384
385    int16_t actLength = GetArcLength();
386    if ((arcLabelText_->GetHorAlign() != TEXT_ALIGNMENT_LEFT) && (actLength < maxLength)) {
387        float gapLength = maxLength - actLength;
388        if (arcLabelText_->GetHorAlign() == TEXT_ALIGNMENT_CENTER) {
389            gapLength = gapLength / 2; // 2: half
390        }
391        arcTextInfo_.startAngle += TypedText::GetAngleForArcLen(gapLength, letterHeight, arcTextInfo_.radius,
392                                                                arcTextInfo_.direct, orientation_);
393    }
394
395    int16_t maxTextLength = arcLabelText_->GetMetaTextWidth(*style_);
396
397    float maxTextAngle = 0.0f;
398    if (compatibilityMode_) {
399        maxTextAngle = TypedText::GetAngleForArcLen(maxTextLength, letterHeight, arcTextInfo_.radius,
400            arcTextInfo_.direct, orientation_);
401    } else {
402        maxTextAngle = TypedText::GetAngleForArcLen(maxTextLength, style_->letterSpace_, arcTextInfo_.radius);
403        maxTextAngle = arcLabelText_->GetDirect() == TEXT_DIRECT_RTL ? -maxTextAngle : maxTextAngle;
404    }
405
406    if (arcLabelText_->GetDirect() == TEXT_DIRECT_LTR) {
407        animator_.secondLapOffsetAngle_ = -maxTextAngle;
408    } else if (arcLabelText_->GetDirect() == TEXT_DIRECT_RTL) {
409        animator_.secondLapOffsetAngle_ = maxTextAngle;
410    }
411}
412
413uint16_t UIArcLabel::GetArcLength()
414{
415    const char* text = arcLabelText_->GetText();
416    if (text == nullptr) {
417        return 0;
418    }
419
420    return TypedText::GetTextWidth(&text[arcTextInfo_.lineStart], arcLabelText_->GetFontId(),
421                                   arcLabelText_->GetFontSize(), (arcTextInfo_.lineEnd - arcTextInfo_.lineStart),
422                                   style_->letterSpace_);
423}
424
425void UIArcLabel::Start()
426{
427    if (arcLabelText_->GetDirect() == TEXT_DIRECT_RTL) {
428        arcLabelText_->SetAlign(TEXT_ALIGNMENT_RIGHT, TEXT_ALIGNMENT_CENTER);
429    } else {
430        arcLabelText_->SetAlign(TEXT_ALIGNMENT_LEFT, TEXT_ALIGNMENT_CENTER);
431    }
432    if (hasAnimator_) {
433        static_cast<ArcLabelAnimator*>(animator_.animator)->SetRollCount(animator_.rollCount);
434    } else {
435        ArcLabelAnimator* animator = new ArcLabelAnimator(animator_.rollCount, this);
436        if (animator == nullptr) {
437            GRAPHIC_LOGE("new ArcLabelAnimator fail");
438            return;
439        }
440        animator->SetRollSpeed(animator_.speed);
441        animator->RegisterScrollListener(animator_.scrollListener);
442        animator_.animator = animator;
443        hasAnimator_ = true;
444    }
445    animator_.animator->Start();
446}
447
448void UIArcLabel::Stop()
449{
450    if (hasAnimator_) {
451        static_cast<ArcLabelAnimator*>(animator_.animator)->Stop();
452    }
453}
454
455void UIArcLabel::SetRollCount(const uint16_t rollCount)
456{
457    if (hasAnimator_) {
458        static_cast<ArcLabelAnimator*>(animator_.animator)->SetRollCount(rollCount);
459    } else {
460        animator_.rollCount = rollCount;
461    }
462}
463
464void UIArcLabel::RegisterScrollListener(ArcLabelScrollListener* scrollListener)
465{
466    if (hasAnimator_) {
467        static_cast<ArcLabelAnimator*>(animator_.animator)->RegisterScrollListener(scrollListener);
468    } else {
469        animator_.scrollListener = scrollListener;
470    }
471}
472
473void UIArcLabel::SetRollSpeed(const uint16_t speed)
474{
475    if (hasAnimator_) {
476        static_cast<ArcLabelAnimator*>(animator_.animator)->SetRollSpeed(speed);
477    } else {
478        animator_.speed = speed;
479    }
480}
481
482uint16_t UIArcLabel::GetRollSpeed() const
483{
484    if (hasAnimator_) {
485        return static_cast<ArcLabelAnimator*>(animator_.animator)->GetRollSpeed();
486    }
487
488    return animator_.speed;
489}
490} // namespace OHOS
491