1 /*
2  * Copyright (c) 2021-2022 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 "core/components/clock/render_clock.h"
17 
18 #include "base/i18n/localization.h"
19 #include "core/components/clock/clock_component.h"
20 #include "core/components/image/image_component.h"
21 
22 namespace OHOS::Ace {
23 namespace {
24 
25 constexpr double HOUR_HAND_RATIO = 0.062; // [width-of-clock-hand-image] / [width-of-clock-face-image]
26 constexpr double RADIAN_FOR_ONE_HOUR = 30 * 3.1415926 / 180.0;
27 constexpr double DIGIT_SIZE_RATIO_UPPER_BOUND = 1.0 / 7.0;
28 constexpr double DIGIT_RADIUS_RATIO_UPPER_BOUND = 1.0;
29 constexpr double EPSILON = 0.000001;
30 constexpr int32_t TOTAL_HOURS_OF_ANALOG_CLOCK = 12;
31 
UseDaySourceIfEmpty(const std::string & daySource,std::string & nightSource)32 void UseDaySourceIfEmpty(const std::string& daySource, std::string& nightSource)
33 {
34     if (nightSource.empty()) {
35         nightSource = daySource;
36     }
37 }
38 
GenerateClockHandLoadFailCallback(const RefPtr<RenderImage> & renderClockHand,double hoursWest,const std::string & handType,InternalResource::ResourceId resourceIdForDay,InternalResource::ResourceId resourceIdForNight)39 std::function<void()> GenerateClockHandLoadFailCallback(const RefPtr<RenderImage>& renderClockHand, double hoursWest,
40     const std::string& handType, InternalResource::ResourceId resourceIdForDay,
41     InternalResource::ResourceId resourceIdForNight)
42 {
43     auto clockHandLoadFailCallback = [wp = AceType::WeakClaim(AceType::RawPtr(renderClockHand)), hoursWest, handType,
44         resourceIdForDay, resourceIdForNight]() {
45         auto renderClockHand = wp.Upgrade();
46         if (!renderClockHand) {
47             LOGE("renderClockHand upgrade fail when try handle clock hand load fail event");
48             return;
49         }
50         RefPtr<ImageComponent> imageComponent = AceType::MakeRefPtr<ImageComponent>("");
51         auto resourceId = IsDayTime(GetTimeOfNow(hoursWest)) ? resourceIdForDay : resourceIdForNight;
52         imageComponent->SetResourceId(resourceId);
53         renderClockHand->Update(imageComponent);
54         LOGE("clock hand load fail event triggered, using internal clock hand source, handType: %{public}s",
55             handType.c_str());
56     };
57     return clockHandLoadFailCallback;
58 }
59 
60 } // namespace
61 
RenderClock()62 RenderClock::RenderClock()
63 {
64     for (int32_t i = 1; i <= TOTAL_HOURS_OF_ANALOG_CLOCK; i++) {
65         auto digitStr = Localization::GetInstance()->NumberFormat(i);
66         auto textComponent = AceType::MakeRefPtr<TextComponent>(digitStr);
67         auto renderDigit = AceType::DynamicCast<RenderText>(RenderText::Create());
68         AddChild(renderDigit);
69         digitRenderNodes_.emplace_back(renderDigit);
70         digitComponentNodes_.emplace_back(textComponent);
71         radians_.emplace_back(i * RADIAN_FOR_ONE_HOUR);
72     }
73 
74     renderClockFace_ = AceType::DynamicCast<RenderImage>(RenderImage::Create());
75     AddChild(renderClockFace_);
76 
77     renderClockHand_ = AceType::DynamicCast<RenderClockHand>(RenderClockHand::Create());
78     renderClockHand_->SetDayToNightCallback([wp = WeakClaim(this)]() {
79         auto renderClock = wp.Upgrade();
80         if (renderClock) {
81             renderClock->UseNightConfig();
82             renderClock->MarkNeedLayout();
83         }
84     });
85     renderClockHand_->SetNightToDayCallback([wp = WeakClaim(this)]() {
86         auto renderClock = wp.Upgrade();
87         if (renderClock) {
88             renderClock->UseDayConfig();
89             renderClock->MarkNeedLayout();
90         }
91     });
92     renderClockHand_->SetAccessibilityTimeCallback([wp = WeakClaim(this)](double hour, double minute) {
93         auto renderClock = wp.Upgrade();
94         if (renderClock) {
95             renderClock->UpdateAccessibilityInfo(hour, minute);
96         }
97     });
98     renderHourHand_ = AceType::DynamicCast<RenderImage>(RenderImage::Create());
99     renderMinuteHand_ = AceType::DynamicCast<RenderImage>(RenderImage::Create());
100     renderSecondHand_ = AceType::DynamicCast<RenderImage>(RenderImage::Create());
101     AddChild(renderClockHand_);
102 }
103 
Update(const RefPtr<Component> & component)104 void RenderClock::Update(const RefPtr<Component>& component)
105 {
106     RefPtr<ClockComponent> clockComponent = AceType::DynamicCast<ClockComponent>(component);
107     if (clockComponent == nullptr) {
108         LOGE("clock component is null!");
109         return;
110     }
111     declaration_ = clockComponent->GetDeclaration();
112     if (declaration_ == nullptr) {
113         LOGE("clock declaration is null!");
114         return;
115     }
116     auto inputDigitSizeRatio = declaration_->GetDigitSizeRatio();
117     digitSizeRatio_ = InRegion(EPSILON, DIGIT_SIZE_RATIO_UPPER_BOUND, inputDigitSizeRatio) ? inputDigitSizeRatio
118                                                                                            : digitSizeRatio_;
119     auto inputDigitRadiusRatio = declaration_->GetDigitRadiusRatio();
120     digitRadiusRatio_ = InRegion(EPSILON, DIGIT_RADIUS_RATIO_UPPER_BOUND, inputDigitRadiusRatio)
121                             ? inputDigitRadiusRatio
122                             : digitRadiusRatio_;
123 
124     // update attributes and styles for night mode
125     clockFaceNightSrc_ = declaration_->GetClockFaceNightSrc();
126     hourHandNightSrc_ = declaration_->GetHourHandNightSrc();
127     minuteHandNightSrc_ = declaration_->GetMinuteHandNightSrc();
128     secondHandNightSrc_ = declaration_->GetSecondHandNightSrc();
129     digitColorNight_ = declaration_->GetDigitColorNight();
130 
131     CheckNightConfig();
132 
133     double hoursWest = declaration_->GetHoursWest();
134     auto timeOfNow = GetTimeOfNow(hoursWest);
135     IsDayTime(timeOfNow) ? UseDayConfig() : UseNightConfig();
136     renderHourHand_->SetLoadFailCallback(GenerateClockHandLoadFailCallback(renderHourHand_, hoursWest, "hour",
137         InternalResource::ResourceId::FA_CLOCK_WIDGET_HOUR,
138         InternalResource::ResourceId::FA_BLACK_CLOCK_WIDGET_HOUR));
139     renderMinuteHand_->SetLoadFailCallback(GenerateClockHandLoadFailCallback(renderMinuteHand_, hoursWest, "minute",
140         InternalResource::ResourceId::FA_CLOCK_WIDGET_MINUTE,
141         InternalResource::ResourceId::FA_BLACK_CLOCK_WIDGET_MINUTE));
142     renderSecondHand_->SetLoadFailCallback(GenerateClockHandLoadFailCallback(renderSecondHand_, hoursWest, "second",
143         InternalResource::ResourceId::FA_CLOCK_WIDGET_SECOND,
144         InternalResource::ResourceId::FA_BLACK_CLOCK_WIDGET_SECOND));
145 
146     renderClockHand_->SetHoursWest(declaration_->GetHoursWest());
147     // update accessibility text when hour changed
148     UpdateAccessibilityInfo(timeOfNow.hour24_, timeOfNow.minute_);
149     renderClockHand_->Attach(GetContext());
150     renderClockHand_->SetOnHourCallback(
151         AceAsyncEvent<void(const std::string&)>::Create(declaration_->GetOnHourChangeEvent(), context_));
152     if (!setScreenCallback_) {
153         auto context = context_.Upgrade();
154         if (!context) {
155             return;
156         }
157         context->AddScreenOnEvent([wp = WeakPtr<RenderClockHand>(renderClockHand_)]() {
158             auto renderClockHand = wp.Upgrade();
159             if (renderClockHand) {
160                 renderClockHand->SetNeedStop(false);
161             }
162         });
163         context->AddScreenOffEvent([wp = WeakPtr<RenderClockHand>(renderClockHand_)]() {
164             auto renderClockHand = wp.Upgrade();
165             if (renderClockHand) {
166                 renderClockHand->SetNeedStop(true);
167             }
168         });
169         setScreenCallback_ = true;
170     }
171     MarkNeedLayout();
172 }
173 
UseDayConfig()174 void RenderClock::UseDayConfig()
175 {
176     UpdateRenderImage(renderClockFace_, declaration_->GetClockFaceSrc());
177 
178     UpdateRenderImage(renderHourHand_, declaration_->GetHourHandSrc());
179     renderClockHand_->SetHourHand(renderHourHand_);
180     UpdateRenderImage(renderMinuteHand_, declaration_->GetMinuteHandSrc());
181     renderClockHand_->SetMinuteHand(renderMinuteHand_);
182     UpdateRenderImage(renderSecondHand_, declaration_->GetSecondHandSrc());
183     renderClockHand_->SetSecondHand(renderSecondHand_);
184     renderClockHand_->SetIsDay(true);
185 }
186 
UseNightConfig()187 void RenderClock::UseNightConfig()
188 {
189     UpdateRenderImage(renderClockFace_, clockFaceNightSrc_);
190 
191     UpdateRenderImage(renderHourHand_, hourHandNightSrc_);
192     renderClockHand_->SetHourHand(renderHourHand_);
193     UpdateRenderImage(renderMinuteHand_, minuteHandNightSrc_);
194     renderClockHand_->SetMinuteHand(renderMinuteHand_);
195     UpdateRenderImage(renderSecondHand_, secondHandNightSrc_);
196     renderClockHand_->SetSecondHand(renderSecondHand_);
197     renderClockHand_->SetIsDay(false);
198 }
199 
CheckNightConfig()200 void RenderClock::CheckNightConfig()
201 {
202     UseDaySourceIfEmpty(declaration_->GetClockFaceSrc(), clockFaceNightSrc_);
203     UseDaySourceIfEmpty(declaration_->GetHourHandSrc(), hourHandNightSrc_);
204     UseDaySourceIfEmpty(declaration_->GetMinuteHandSrc(), minuteHandNightSrc_);
205     UseDaySourceIfEmpty(declaration_->GetSecondHandSrc(), secondHandNightSrc_);
206     if (digitColorNight_ == Color::TRANSPARENT) {
207         digitColorNight_ = declaration_->GetDigitColor();
208     }
209 }
210 
UpdateAccessibilityInfo(double hour,double minute)211 void RenderClock::UpdateAccessibilityInfo(double hour, double minute)
212 {
213     auto node = GetAccessibilityNode().Upgrade();
214     if (!node) {
215         return;
216     }
217     std::string prefix = minute < 10 ? "0" : "";
218     std::string minuteToString =  prefix.append(std::to_string(static_cast<int32_t>(minute)));
219     std::string content = std::to_string(static_cast<int32_t>(hour)).append(":").append(minuteToString);
220     node->SetText(content);
221 
222     auto context = context_.Upgrade();
223     if (context) {
224         AccessibilityEvent accessibilityEvent;
225         accessibilityEvent.nodeId = node->GetNodeId();
226         accessibilityEvent.eventType = "textchange";
227         context->SendEventToAccessibility(accessibilityEvent);
228     }
229 }
230 
UpdateRenderText(double digitSize,const Color & digitColor)231 void RenderClock::UpdateRenderText(double digitSize, const Color& digitColor)
232 {
233     if (digitComponentNodes_.size() != digitRenderNodes_.size()) {
234         LOGE("Size of [digitComponentNodes] and [digitRenderNodes] does not match! Please check!");
235         return;
236     }
237     TextStyle textStyle;
238     textStyle.SetAllowScale(false);
239     textStyle.SetTextColor(digitColor);
240     textStyle.SetFontSize(Dimension(digitSize));
241     textStyle.SetFontFamilies(declaration_->GetFontFamilies());
242 
243     for (size_t i = 0; i < digitRenderNodes_.size(); i++) {
244         const auto& textComponent = digitComponentNodes_[i];
245         textComponent->SetTextStyle(textStyle);
246         textComponent->SetFocusColor(digitColor);
247         digitRenderNodes_[i]->Attach(GetContext());
248         digitRenderNodes_[i]->Update(textComponent);
249     }
250 }
251 
UpdateRenderImage(RefPtr<RenderImage> & renderImage,const std::string & imageSrc)252 void RenderClock::UpdateRenderImage(RefPtr<RenderImage>& renderImage, const std::string& imageSrc)
253 {
254     RefPtr<ImageComponent> imageComponent = AceType::MakeRefPtr<ImageComponent>(imageSrc);
255     renderImage->Attach(GetContext());
256     renderImage->Update(imageComponent);
257 }
258 
PerformLayout()259 void RenderClock::PerformLayout()
260 {
261     defaultSize_ = Dimension(NormalizeToPx(defaultSize_), DimensionUnit::PX);
262     CalculateLayoutSize();
263     SetLayoutSize(GetLayoutParam().Constrain(drawSize_));
264     LayoutClockImage(renderClockFace_, drawSize_);
265     auto textColor = renderClockHand_->GetIsDay() ? declaration_->GetDigitColor() : digitColorNight_;
266     if (declaration_->GetShowDigit()) {
267         LayoutParam textLayoutParam = GetLayoutParam();
268         textLayoutParam.SetMinSize(Size());
269         UpdateRenderText(drawSize_.Width() * digitSizeRatio_, textColor);
270         for (const auto& renderDigit : digitRenderNodes_) {
271             renderDigit->Layout(textLayoutParam);
272         }
273     }
274 
275     LayoutParam layoutParam = GetLayoutParam();
276     layoutParam.SetMaxSize(drawSize_);
277     renderClockHand_->Layout(layoutParam);
278 
279     paintOffset_ = Alignment::GetAlignPosition(GetLayoutSize(), drawSize_, Alignment::CENTER);
280 }
281 
CalculateLayoutSize()282 void RenderClock::CalculateLayoutSize()
283 {
284     auto maxSize = GetLayoutParam().GetMaxSize();
285     if (!maxSize.IsValid()) {
286         LOGE("LayoutParam invalid!");
287         return;
288     }
289     uint8_t infiniteStatus =
290         (static_cast<uint8_t>(maxSize.IsWidthInfinite()) << 1) | static_cast<uint8_t>(maxSize.IsHeightInfinite());
291     drawSize_ = maxSize;
292     switch (infiniteStatus) {
293         case 0b00: // both width and height are valid
294             break;
295         case 0b01: // width is valid but height is infinite
296             drawSize_.SetHeight(defaultSize_.Value());
297             break;
298         case 0b10: // height is valid but width is infinite
299             drawSize_.SetWidth(defaultSize_.Value());
300             break;
301         case 0b11: // both width and height are infinite
302         default:
303             drawSize_ = Size(defaultSize_.Value(), defaultSize_.Value());
304             break;
305     }
306     double shorterEdge = std::min(drawSize_.Width(), drawSize_.Height());
307     drawSize_ = Size(shorterEdge, shorterEdge);
308 }
309 
LayoutClockImage(const RefPtr<RenderImage> & renderImage,const Size & imageComponentSize)310 void RenderClock::LayoutClockImage(const RefPtr<RenderImage>& renderImage, const Size& imageComponentSize)
311 {
312     LayoutParam imageLayoutParam;
313     imageLayoutParam.SetFixedSize(imageComponentSize);
314     renderImage->SetImageComponentSize(imageComponentSize);
315     renderImage->Layout(imageLayoutParam);
316 }
317 
PerformLayout()318 void RenderClockHand::PerformLayout()
319 {
320     SetLayoutSize(GetLayoutParam().GetMaxSize());
321     auto clockSize = GetLayoutSize();
322     RenderClock::LayoutClockImage(renderHourHand_, Size(clockSize.Width() * HOUR_HAND_RATIO, clockSize.Height()));
323     RenderClock::LayoutClockImage(renderMinuteHand_, Size(clockSize.Width() * HOUR_HAND_RATIO, clockSize.Height()));
324     RenderClock::LayoutClockImage(renderSecondHand_, Size(clockSize.Width() * HOUR_HAND_RATIO, clockSize.Height()));
325 }
326 
327 } // namespace OHOS::Ace
328