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