1 /*
2 * Copyright (c) 2021-2023 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 "bridge/declarative_frontend/jsview/js_gauge.h"
17
18 #include <string>
19
20 #include "base/log/ace_scoring_log.h"
21 #include "base/memory/ace_type.h"
22 #include "bridge/declarative_frontend/jsview/js_interactable_view.h"
23 #include "bridge/declarative_frontend/jsview/js_linear_gradient.h"
24 #include "bridge/declarative_frontend/jsview/js_utils.h"
25 #include "bridge/declarative_frontend/jsview/models/gauge_model_impl.h"
26 #include "core/components_ng/base/view_stack_model.h"
27 #include "core/components_ng/pattern/gauge/gauge_model_ng.h"
28
29 namespace OHOS::Ace {
30
31 std::unique_ptr<GaugeModel> GaugeModel::instance_ = nullptr;
32 std::mutex GaugeModel::mutex_;
33 constexpr double DEFAULT_GAUGE_VALUE = 0;
34 constexpr double DEFAULT_GAUGE_MIN = 0;
35 constexpr double DEFAULT_GAUGE_MAX = 100;
36
GetInstance()37 GaugeModel* GaugeModel::GetInstance()
38 {
39 if (!instance_) {
40 std::lock_guard<std::mutex> lock(mutex_);
41 if (!instance_) {
42 #ifdef NG_BUILD
43 instance_.reset(new NG::GaugeModelNG());
44 #else
45 if (Container::IsCurrentUseNewPipeline()) {
46 instance_.reset(new NG::GaugeModelNG());
47 } else {
48 instance_.reset(new Framework::GaugeModelImpl());
49 }
50 #endif
51 }
52 }
53 return instance_.get();
54 }
55
56 } // namespace OHOS::Ace
57 namespace OHOS::Ace::Framework {
58 constexpr Color ERROR_COLOR = Color(0xFFE84026);
59 constexpr float FIX_ANGLE = 720.0f;
60
JSBind(BindingTarget globalObj)61 void JSGauge::JSBind(BindingTarget globalObj)
62 {
63 JSClass<JSGauge>::Declare("Gauge");
64 JSClass<JSGauge>::StaticMethod("create", &JSGauge::Create);
65
66 JSClass<JSGauge>::StaticMethod("value", &JSGauge::SetValue);
67 JSClass<JSGauge>::StaticMethod("startAngle", &JSGauge::SetStartAngle);
68 JSClass<JSGauge>::StaticMethod("endAngle", &JSGauge::SetEndAngle);
69 JSClass<JSGauge>::StaticMethod("colors", &JSGauge::SetColors);
70 JSClass<JSGauge>::StaticMethod("strokeWidth", &JSGauge::SetStrokeWidth);
71 JSClass<JSGauge>::StaticMethod("labelConfig", &JSGauge::SetLabelConfig);
72 JSClass<JSGauge>::StaticMethod("trackShadow", &JSGauge::SetShadowOptions);
73 JSClass<JSGauge>::StaticMethod("indicator", &JSGauge::SetIndicator);
74 JSClass<JSGauge>::StaticMethod("description", &JSGauge::SetDescription);
75 JSClass<JSGauge>::StaticMethod("onClick", &JSInteractableView::JsOnClick);
76 JSClass<JSGauge>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
77 JSClass<JSGauge>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
78 JSClass<JSGauge>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
79 JSClass<JSGauge>::StaticMethod("onAttach", &JSInteractableView::JsOnAttach);
80 JSClass<JSGauge>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
81 JSClass<JSGauge>::StaticMethod("onDetach", &JSInteractableView::JsOnDetach);
82 JSClass<JSGauge>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
83
84 JSClass<JSGauge>::InheritAndBind<JSViewAbstract>(globalObj);
85 }
86
Create(const JSCallbackInfo & info)87 void JSGauge::Create(const JSCallbackInfo& info)
88 {
89 if (!info[0]->IsObject()) {
90 GaugeModel::GetInstance()->Create(DEFAULT_GAUGE_VALUE, DEFAULT_GAUGE_MIN, DEFAULT_GAUGE_MAX);
91 GaugeModel::GetInstance()->SetIsShowLimitValue(true);
92 return;
93 }
94
95 auto paramObject = JSRef<JSObject>::Cast(info[0]);
96 auto value = paramObject->GetProperty("value");
97 auto min = paramObject->GetProperty("min");
98 auto max = paramObject->GetProperty("max");
99
100 double gaugeMin = min->IsNumber() ? min->ToNumber<double>() : DEFAULT_GAUGE_MIN;
101 double gaugeMax = max->IsNumber() ? max->ToNumber<double>() : DEFAULT_GAUGE_MAX;
102 double gaugeValue = value->IsNumber() ? value->ToNumber<double>() : DEFAULT_GAUGE_VALUE;
103 if (LessNotEqual(gaugeMax, gaugeMin)) {
104 gaugeMin = NG::DEFAULT_MIN_VALUE;
105 gaugeMax = NG::DEFAULT_MAX_VALUE;
106 }
107
108 if (LessNotEqual(gaugeValue, gaugeMin) || GreatNotEqual(gaugeValue, gaugeMax)) {
109 gaugeValue = gaugeMin;
110 }
111 GaugeModel::GetInstance()->Create(gaugeValue, gaugeMin, gaugeMax);
112 if (min->IsNumber() || max->IsNumber()) {
113 GaugeModel::GetInstance()->SetIsShowLimitValue(true);
114 } else {
115 GaugeModel::GetInstance()->SetIsShowLimitValue(false);
116 }
117 }
118
SetValue(const JSCallbackInfo & info)119 void JSGauge::SetValue(const JSCallbackInfo& info)
120 {
121 float value = NG::DEFAULT_MIN_VALUE;
122 if (info[0]->IsNumber()) {
123 value = info[0]->ToNumber<float>();
124 }
125 GaugeModel::GetInstance()->SetValue(value);
126 }
127
SetStartAngle(const JSCallbackInfo & info)128 void JSGauge::SetStartAngle(const JSCallbackInfo& info)
129 {
130 float startAngle = NG::DEFAULT_START_DEGREE;
131 if (info[0]->IsNumber()) {
132 startAngle = std::fmod(info[0]->ToNumber<double>(), FIX_ANGLE);
133 }
134 GaugeModel::GetInstance()->SetStartAngle(startAngle);
135 }
136
SetEndAngle(const JSCallbackInfo & info)137 void JSGauge::SetEndAngle(const JSCallbackInfo& info)
138 {
139 float endAngle = NG::DEFAULT_END_DEGREE;
140 if (info[0]->IsNumber()) {
141 endAngle = std::fmod(info[0]->ToNumber<double>(), FIX_ANGLE);
142 }
143 GaugeModel::GetInstance()->SetEndAngle(endAngle);
144 }
145
SetColors(const JSCallbackInfo & info)146 void JSGauge::SetColors(const JSCallbackInfo& info)
147 {
148 if (!Container::LessThanAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
149 SetGradientColors(info);
150 return;
151 }
152
153 if (!info[0]->IsArray()) {
154 return;
155 }
156 std::vector<Color> colors;
157 std::vector<double> values;
158 std::vector<float> weights;
159 auto jsColor = JSRef<JSArray>::Cast(info[0]);
160 for (size_t i = 0; i < jsColor->Length(); ++i) {
161 JSRef<JSVal> jsValue = jsColor->GetValueAt(i);
162 if (!jsValue->IsArray()) {
163 return;
164 }
165 JSRef<JSArray> tempColors = jsColor->GetValueAt(i);
166 double value = tempColors->GetValueAt(1)->ToNumber<double>();
167 float weight = tempColors->GetValueAt(1)->ToNumber<float>();
168 Color selectedColor;
169 if (!ParseJsColor(tempColors->GetValueAt(0), selectedColor)) {
170 selectedColor = ERROR_COLOR;
171 }
172 colors.push_back(selectedColor);
173 values.push_back(value);
174 if (weight > 0) {
175 weights.push_back(weight);
176 } else {
177 weights.push_back(0.0f);
178 }
179 }
180 GaugeModel::GetInstance()->SetColors(colors, weights);
181 }
182
SetGradientColors(const JSCallbackInfo & info)183 void JSGauge::SetGradientColors(const JSCallbackInfo& info)
184 {
185 if (info[0]->IsNull() || info[0]->IsUndefined()) {
186 GaugeModel::GetInstance()->ResetGradientColors();
187 return;
188 }
189
190 NG::GaugeType type = NG::GaugeType::TYPE_CIRCULAR_MULTI_SEGMENT_GRADIENT;
191 std::vector<NG::ColorStopArray> colors;
192 std::vector<float> weights;
193 if (info[0]->IsArray()) {
194 auto jsColorsArray = JSRef<JSArray>::Cast(info[0]);
195 if (jsColorsArray->Length() == 0) {
196 GaugeModel::GetInstance()->ResetGradientColors();
197 return;
198 }
199
200 for (size_t i = 0; i < jsColorsArray->Length(); ++i) {
201 if (static_cast<int32_t>(i) >= NG::COLORS_MAX_COUNT) {
202 break;
203 }
204 JSRef<JSVal> jsValue = jsColorsArray->GetValueAt(i);
205 if (!jsValue->IsArray()) {
206 continue;
207 }
208 auto tempColors = JSRef<JSArray>::Cast(jsValue);
209 // Get weight
210 float weight = tempColors->GetValueAt(1)->ToNumber<float>();
211 if (NonPositive(weight)) {
212 continue;
213 }
214 weights.push_back(weight);
215 // Get color
216 JSRef<JSVal> jsColorValue = tempColors->GetValueAt(0);
217 ConvertGradientColor(jsColorValue, colors, type);
218 }
219 type = NG::GaugeType::TYPE_CIRCULAR_MULTI_SEGMENT_GRADIENT;
220 SortColorStopOffset(colors);
221 GaugeModel::GetInstance()->SetGradientColors(colors, weights, type);
222 return;
223 }
224 ConvertGradientColor(info[0], colors, type);
225 SortColorStopOffset(colors);
226 GaugeModel::GetInstance()->SetGradientColors(colors, weights, type);
227 }
228
SortColorStopOffset(std::vector<NG::ColorStopArray> & colors)229 void JSGauge::SortColorStopOffset(std::vector<NG::ColorStopArray>& colors)
230 {
231 for (auto& colorStopArray : colors) {
232 std::sort(colorStopArray.begin(), colorStopArray.end(),
233 [](const std::pair<Color, Dimension>& left, const std::pair<Color, Dimension>& right) {
234 return left.second.Value() < right.second.Value();
235 });
236
237 auto iter = std::unique(colorStopArray.begin(), colorStopArray.end(),
238 [](const std::pair<Color, Dimension>& left, const std::pair<Color, Dimension>& right) {
239 return left.second.Value() == right.second.Value();
240 });
241 colorStopArray.erase(iter, colorStopArray.end());
242 }
243 }
244
ConvertGradientColor(const JsiRef<JsiValue> & itemParam,std::vector<NG::ColorStopArray> & colors,NG::GaugeType & type)245 void JSGauge::ConvertGradientColor(
246 const JsiRef<JsiValue>& itemParam, std::vector<NG::ColorStopArray>& colors, NG::GaugeType& type)
247 {
248 if (!itemParam->IsObject()) {
249 type = NG::GaugeType::TYPE_CIRCULAR_MONOCHROME;
250 return ConvertResourceColor(itemParam, colors);
251 }
252
253 JSLinearGradient* jsLinearGradient = JSRef<JSObject>::Cast(itemParam)->Unwrap<JSLinearGradient>();
254 if (!jsLinearGradient) {
255 type = NG::GaugeType::TYPE_CIRCULAR_MONOCHROME;
256 return ConvertResourceColor(itemParam, colors);
257 }
258
259 type = NG::GaugeType::TYPE_CIRCULAR_SINGLE_SEGMENT_GRADIENT;
260 if (jsLinearGradient->GetGradient().size() == 0) {
261 NG::ColorStopArray colorStopArray;
262 colorStopArray.emplace_back(std::make_pair(ERROR_COLOR, Dimension(0.0)));
263 colors.emplace_back(colorStopArray);
264 } else {
265 colors.emplace_back(jsLinearGradient->GetGradient());
266 }
267 }
268
ConvertResourceColor(const JsiRef<JsiValue> & itemParam,std::vector<NG::ColorStopArray> & colors)269 void JSGauge::ConvertResourceColor(const JsiRef<JsiValue>& itemParam, std::vector<NG::ColorStopArray>& colors)
270 {
271 Color color;
272 if (!ParseJsColor(itemParam, color)) {
273 color = ERROR_COLOR;
274 }
275 NG::ColorStopArray colorStopArray;
276 colorStopArray.emplace_back(std::make_pair(color, Dimension(0.0)));
277 colors.emplace_back(colorStopArray);
278 }
279
SetStrokeWidth(const JSCallbackInfo & info)280 void JSGauge::SetStrokeWidth(const JSCallbackInfo& info)
281 {
282 CalcDimension strokeWidth;
283 if (!ParseJsDimensionVpNG(info[0], strokeWidth) || strokeWidth.Unit() == DimensionUnit::PERCENT) {
284 strokeWidth = CalcDimension(0);
285 }
286 GaugeModel::GetInstance()->SetStrokeWidth(strokeWidth);
287 }
288
SetLabelConfig(const JSCallbackInfo & info)289 void JSGauge::SetLabelConfig(const JSCallbackInfo& info)
290 {
291 if (!info[0]->IsObject()) {
292 return;
293 }
294 auto paramObject = JSRef<JSObject>::Cast(info[0]);
295 auto labelText = paramObject->GetProperty("text");
296 auto labelColor = paramObject->GetProperty("color");
297 Color currentColor;
298 ParseJsColor(labelColor, currentColor);
299 if (labelText->IsString()) {
300 GaugeModel::GetInstance()->SetLabelMarkedText(labelText->ToString());
301 }
302 if (ParseJsColor(labelColor, currentColor)) {
303 GaugeModel::GetInstance()->SetMarkedTextColor(currentColor);
304 }
305 }
306
SetShadowOptions(const JSCallbackInfo & info)307 void JSGauge::SetShadowOptions(const JSCallbackInfo& info)
308 {
309 NG::GaugeShadowOptions shadowOptions;
310 if (info[0]->IsNull()) {
311 shadowOptions.isShadowVisible = false;
312 GaugeModel::GetInstance()->SetShadowOptions(shadowOptions);
313 return;
314 }
315
316 if (!info[0]->IsObject()) {
317 GaugeModel::GetInstance()->ResetShadowOptions();
318 return;
319 }
320
321 auto paramObject = JSRef<JSObject>::Cast(info[0]);
322 JSRef<JSVal> jsRadius = paramObject->GetProperty("radius");
323 JSRef<JSVal> jsOffsetX = paramObject->GetProperty("offsetX");
324 JSRef<JSVal> jsOffsetY = paramObject->GetProperty("offsetY");
325
326 double radius = 0.0;
327 if (!ParseJsDouble(jsRadius, radius)) {
328 radius = NG::DEFAULT_GAUGE_SHADOW_RADIUS;
329 }
330
331 if (NonPositive(radius)) {
332 radius = NG::DEFAULT_GAUGE_SHADOW_RADIUS;
333 }
334
335 double offsetX = 0.0;
336 if (!ParseJsDouble(jsOffsetX, offsetX)) {
337 offsetX = NG::DEFAULT_GAUGE_SHADOW_OFFSETX;
338 }
339
340 double offsetY = 0.0;
341 if (!ParseJsDouble(jsOffsetY, offsetY)) {
342 offsetY = NG::DEFAULT_GAUGE_SHADOW_OFFSETY;
343 }
344
345 shadowOptions.radius = radius;
346 shadowOptions.offsetX = offsetX;
347 shadowOptions.offsetY = offsetY;
348
349 GaugeModel::GetInstance()->SetShadowOptions(shadowOptions);
350 }
351
SetDescription(const JSCallbackInfo & info)352 void JSGauge::SetDescription(const JSCallbackInfo& info)
353 {
354 if (info[0]->IsNull()) {
355 GaugeModel::GetInstance()->SetIsShowLimitValue(false);
356 GaugeModel::GetInstance()->SetIsShowDescription(false);
357 return;
358 }
359 if (info[0]->IsUndefined() || !info[0]->IsObject()) {
360 GaugeModel::GetInstance()->SetIsShowLimitValue(true);
361 GaugeModel::GetInstance()->SetIsShowDescription(false);
362 return;
363 }
364
365 auto builderObject = JSRef<JSObject>::Cast(info[0])->GetProperty("builder");
366 if (builderObject->IsFunction()) {
367 GaugeModel::GetInstance()->SetIsShowLimitValue(false);
368 GaugeModel::GetInstance()->SetIsShowDescription(true);
369 ViewStackModel::GetInstance()->NewScope();
370 JsFunction jsBuilderFunc(info.This(), JSRef<JSFunc>::Cast(builderObject));
371 ACE_SCORING_EVENT("Gauge.description.builder");
372 jsBuilderFunc.Execute();
373 auto customNode = ViewStackModel::GetInstance()->Finish();
374 GaugeModel::GetInstance()->SetDescription(customNode);
375 } else {
376 GaugeModel::GetInstance()->SetIsShowLimitValue(true);
377 GaugeModel::GetInstance()->SetIsShowDescription(false);
378 }
379 }
380
SetIndicator(const JSCallbackInfo & info)381 void JSGauge::SetIndicator(const JSCallbackInfo& info)
382 {
383 if (info[0]->IsNull()) {
384 GaugeModel::GetInstance()->SetIsShowIndicator(false);
385 return;
386 }
387
388 if (!info[0]->IsObject()) {
389 GaugeModel::GetInstance()->ResetIndicatorIconPath();
390 GaugeModel::GetInstance()->ResetIndicatorSpace();
391 GaugeModel::GetInstance()->SetIsShowIndicator(true);
392 return;
393 }
394
395 GaugeModel::GetInstance()->SetIsShowIndicator(true);
396 auto paramObject = JSRef<JSObject>::Cast(info[0]);
397 JSRef<JSVal> jsIcon = paramObject->GetProperty("icon");
398 JSRef<JSVal> jsSpace = paramObject->GetProperty("space");
399
400 std::string iconPath;
401 if (ParseJsMedia(jsIcon, iconPath)) {
402 std::string bundleName;
403 std::string moduleName;
404 GetJsMediaBundleInfo(jsIcon, bundleName, moduleName);
405 GaugeModel::GetInstance()->SetIndicatorIconPath(iconPath, bundleName, moduleName);
406 } else {
407 GaugeModel::GetInstance()->ResetIndicatorIconPath();
408 }
409
410 CalcDimension space;
411 if (!ParseJsDimensionVpNG(jsSpace, space, false)) {
412 space = NG::INDICATOR_DISTANCE_TO_TOP;
413 }
414 if (space.IsNegative()) {
415 space = NG::INDICATOR_DISTANCE_TO_TOP;
416 }
417 GaugeModel::GetInstance()->SetIndicatorSpace(space);
418 }
419 } // namespace OHOS::Ace::Framework
420