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