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