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