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