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