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