1 /**
2  * @file lv_gauge.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_gauge.h"
10 #if LV_USE_GAUGE != 0
11 
12 #include "../lv_draw/lv_draw.h"
13 #include "../lv_themes/lv_theme.h"
14 #include "../lv_misc/lv_txt.h"
15 #include "../lv_misc/lv_math.h"
16 #include "../lv_misc/lv_utils.h"
17 #include <stdio.h>
18 #include <string.h>
19 
20 /*********************
21  *      DEFINES
22  *********************/
23 #define LV_GAUGE_DEF_NEEDLE_COLOR LV_COLOR_RED
24 #define LV_GAUGE_DEF_LABEL_COUNT 6
25 #define LV_GAUGE_DEF_LINE_COUNT 21 /*Should be: ((label_cnt - 1) * internal_lines) + 1*/
26 #define LV_GAUGE_DEF_ANGLE 220
27 #define LV_GAUGE_INTERPOLATE_SHIFT 5 /*Interpolate the needle drawing between to degrees*/
28 #define LV_GAUGE_INTERPOLATE_MASK 0x1F
29 
30 /**********************
31  *      TYPEDEFS
32  **********************/
33 
34 /**********************
35  *  STATIC PROTOTYPES
36  **********************/
37 static bool lv_gauge_design(lv_obj_t * gauge, const lv_area_t * mask, lv_design_mode_t mode);
38 static lv_res_t lv_gauge_signal(lv_obj_t * gauge, lv_signal_t sign, void * param);
39 static void lv_gauge_draw_scale(lv_obj_t * gauge, const lv_area_t * mask);
40 static void lv_gauge_draw_needle(lv_obj_t * gauge, const lv_area_t * mask);
41 
42 /**********************
43  *  STATIC VARIABLES
44  **********************/
45 static lv_design_cb_t ancestor_design;
46 static lv_signal_cb_t ancestor_signal;
47 
48 /**********************
49  *      MACROS
50  **********************/
51 
52 /**********************
53  *   GLOBAL FUNCTIONS
54  **********************/
55 
56 /**
57  * Create a gauge objects
58  * @param par pointer to an object, it will be the parent of the new gauge
59  * @param copy pointer to a gauge object, if not NULL then the new object will be copied from it
60  * @return pointer to the created gauge
61  */
lv_gauge_create(lv_obj_t * par,const lv_obj_t * copy)62 lv_obj_t * lv_gauge_create(lv_obj_t * par, const lv_obj_t * copy)
63 {
64     LV_LOG_TRACE("gauge create started");
65 
66     /*Create the ancestor gauge*/
67     lv_obj_t * new_gauge = lv_lmeter_create(par, copy);
68     lv_mem_assert(new_gauge);
69     if(new_gauge == NULL) return NULL;
70 
71     /*Allocate the gauge type specific extended data*/
72     lv_gauge_ext_t * ext = lv_obj_allocate_ext_attr(new_gauge, sizeof(lv_gauge_ext_t));
73     lv_mem_assert(ext);
74     if(ext == NULL) return NULL;
75 
76     /*Initialize the allocated 'ext' */
77     ext->needle_count  = 0;
78     ext->values        = NULL;
79     ext->needle_colors = NULL;
80     ext->label_count   = LV_GAUGE_DEF_LABEL_COUNT;
81     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(new_gauge);
82     if(ancestor_design == NULL) ancestor_design = lv_obj_get_design_cb(new_gauge);
83 
84     /*The signal and design functions are not copied so set them here*/
85     lv_obj_set_signal_cb(new_gauge, lv_gauge_signal);
86     lv_obj_set_design_cb(new_gauge, lv_gauge_design);
87 
88     /*Init the new gauge gauge*/
89     if(copy == NULL) {
90         lv_gauge_set_scale(new_gauge, LV_GAUGE_DEF_ANGLE, LV_GAUGE_DEF_LINE_COUNT, LV_GAUGE_DEF_LABEL_COUNT);
91         lv_gauge_set_needle_count(new_gauge, 1, NULL);
92         lv_gauge_set_critical_value(new_gauge, 80);
93         lv_obj_set_size(new_gauge, 2 * LV_DPI, 2 * LV_DPI);
94 
95         /*Set the default styles*/
96         lv_theme_t * th = lv_theme_get_current();
97         if(th) {
98             lv_gauge_set_style(new_gauge, LV_GAUGE_STYLE_MAIN, th->style.gauge);
99         } else {
100             lv_gauge_set_style(new_gauge, LV_GAUGE_STYLE_MAIN, &lv_style_pretty_color);
101         }
102     }
103     /*Copy an existing gauge*/
104     else {
105         lv_gauge_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
106         lv_gauge_set_needle_count(new_gauge, copy_ext->needle_count, copy_ext->needle_colors);
107 
108         uint8_t i;
109         for(i = 0; i < ext->needle_count; i++) {
110             ext->values[i] = copy_ext->values[i];
111         }
112         ext->label_count = copy_ext->label_count;
113         /*Refresh the style with new signal function*/
114         lv_obj_refresh_style(new_gauge);
115     }
116 
117     LV_LOG_INFO("gauge created");
118 
119     return new_gauge;
120 }
121 
122 /*=====================
123  * Setter functions
124  *====================*/
125 
126 /**
127  * Set the number of needles
128  * @param gauge pointer to gauge object
129  * @param needle_cnt new count of needles
130  * @param colors an array of colors for needles (with 'num' elements)
131  */
lv_gauge_set_needle_count(lv_obj_t * gauge,uint8_t needle_cnt,const lv_color_t colors[])132 void lv_gauge_set_needle_count(lv_obj_t * gauge, uint8_t needle_cnt, const lv_color_t colors[])
133 {
134     lv_gauge_ext_t * ext = lv_obj_get_ext_attr(gauge);
135 
136     if(ext->needle_count != needle_cnt) {
137         if(ext->values != NULL) {
138             lv_mem_free(ext->values);
139             ext->values = NULL;
140         }
141 
142         ext->values = lv_mem_realloc(ext->values, needle_cnt * sizeof(int16_t));
143         lv_mem_assert(ext->values);
144         if(ext->values == NULL) return;
145 
146         int16_t min = lv_gauge_get_min_value(gauge);
147         uint8_t n;
148         for(n = ext->needle_count; n < needle_cnt; n++) {
149             ext->values[n] = min;
150         }
151 
152         ext->needle_count = needle_cnt;
153     }
154 
155     ext->needle_colors = colors;
156     lv_obj_invalidate(gauge);
157 }
158 
159 /**
160  * Set the value of a needle
161  * @param gauge pointer to a gauge
162  * @param needle_id the id of the needle
163  * @param value the new value
164  */
lv_gauge_set_value(lv_obj_t * gauge,uint8_t needle_id,int16_t value)165 void lv_gauge_set_value(lv_obj_t * gauge, uint8_t needle_id, int16_t value)
166 {
167     lv_gauge_ext_t * ext = lv_obj_get_ext_attr(gauge);
168 
169     if(needle_id >= ext->needle_count) return;
170     if(ext->values[needle_id] == value) return;
171 
172     int16_t min = lv_gauge_get_min_value(gauge);
173     int16_t max = lv_gauge_get_max_value(gauge);
174 
175     if(value > max)
176         value = max;
177     else if(value < min)
178         value = min;
179 
180     ext->values[needle_id] = value;
181 
182     lv_obj_invalidate(gauge);
183 }
184 
185 /**
186  * Set the scale settings of a gauge
187  * @param gauge pointer to a gauge object
188  * @param angle angle of the scale (0..360)
189  * @param line_cnt count of scale lines.
190  * The get a given "subdivision" lines between label, `line_cnt` = (sub_div + 1) * (label_cnt - 1) +
191  * 1
192  * @param label_cnt count of scale labels.
193  */
lv_gauge_set_scale(lv_obj_t * gauge,uint16_t angle,uint8_t line_cnt,uint8_t label_cnt)194 void lv_gauge_set_scale(lv_obj_t * gauge, uint16_t angle, uint8_t line_cnt, uint8_t label_cnt)
195 {
196     lv_lmeter_set_scale(gauge, angle, line_cnt);
197 
198     lv_gauge_ext_t * ext = lv_obj_get_ext_attr(gauge);
199     ext->label_count     = label_cnt;
200     lv_obj_invalidate(gauge);
201 }
202 
203 /*=====================
204  * Getter functions
205  *====================*/
206 
207 /**
208  * Get the value of a needle
209  * @param gauge pointer to gauge object
210  * @param needle the id of the needle
211  * @return the value of the needle [min,max]
212  */
lv_gauge_get_value(const lv_obj_t * gauge,uint8_t needle)213 int16_t lv_gauge_get_value(const lv_obj_t * gauge, uint8_t needle)
214 {
215     lv_gauge_ext_t * ext = lv_obj_get_ext_attr(gauge);
216     int16_t min          = lv_gauge_get_min_value(gauge);
217 
218     if(needle >= ext->needle_count) return min;
219 
220     return ext->values[needle];
221 }
222 
223 /**
224  * Get the count of needles on a gauge
225  * @param gauge pointer to gauge
226  * @return count of needles
227  */
lv_gauge_get_needle_count(const lv_obj_t * gauge)228 uint8_t lv_gauge_get_needle_count(const lv_obj_t * gauge)
229 {
230     lv_gauge_ext_t * ext = lv_obj_get_ext_attr(gauge);
231     return ext->needle_count;
232 }
233 
234 /**
235  * Set the number of labels (and the thicker lines too)
236  * @param gauge pointer to a gauge object
237  * @return count of labels
238  */
lv_gauge_get_label_count(const lv_obj_t * gauge)239 uint8_t lv_gauge_get_label_count(const lv_obj_t * gauge)
240 {
241     lv_gauge_ext_t * ext = lv_obj_get_ext_attr(gauge);
242     return ext->label_count;
243 }
244 
245 /**********************
246  *   STATIC FUNCTIONS
247  **********************/
248 
249 /**
250  * Handle the drawing related tasks of the gauges
251  * @param gauge pointer to an object
252  * @param mask the object will be drawn only in this area
253  * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
254  *                                  (return 'true' if yes)
255  *             LV_DESIGN_DRAW: draw the object (always return 'true')
256  *             LV_DESIGN_DRAW_POST: drawing after every children are drawn
257  * @param return true/false, depends on 'mode'
258  */
lv_gauge_design(lv_obj_t * gauge,const lv_area_t * mask,lv_design_mode_t mode)259 static bool lv_gauge_design(lv_obj_t * gauge, const lv_area_t * mask, lv_design_mode_t mode)
260 {
261 
262     /*Return false if the object is not covers the mask_p area*/
263     if(mode == LV_DESIGN_COVER_CHK) {
264         return false;
265     }
266     /*Draw the object*/
267     else if(mode == LV_DESIGN_DRAW_MAIN) {
268 
269         /* Store the real pointer because of 'lv_group'
270          * If the object is in focus 'lv_obj_get_style()' will give a pointer to tmp style
271          * and to the real object style. It is important because of style change tricks below*/
272         const lv_style_t * style_ori_p = gauge->style_p;
273         const lv_style_t * style       = lv_obj_get_style(gauge);
274         lv_gauge_ext_t * ext           = lv_obj_get_ext_attr(gauge);
275 
276         lv_gauge_draw_scale(gauge, mask);
277 
278         /*Draw the ancestor line meter with max value to show the rainbow like line colors*/
279         uint16_t line_cnt_tmp = ext->lmeter.line_cnt;
280         ancestor_design(gauge, mask, mode); /*To draw lines*/
281 
282         /*Temporally modify the line meter to draw longer lines where labels are*/
283         lv_style_t style_tmp;
284         lv_style_copy(&style_tmp, style);
285         ext->lmeter.line_cnt         = ext->label_count;                 /*Only to labels*/
286         style_tmp.body.padding.left  = style_tmp.body.padding.left * 2;  /*Longer lines*/
287         style_tmp.body.padding.right = style_tmp.body.padding.right * 2; /*Longer lines*/
288         gauge->style_p               = &style_tmp;
289 
290         ancestor_design(gauge, mask, mode); /*To draw lines*/
291 
292         ext->lmeter.line_cnt = line_cnt_tmp; /*Restore the parameters*/
293         gauge->style_p       = style_ori_p;  /*Restore the ORIGINAL style pointer*/
294 
295         lv_gauge_draw_needle(gauge, mask);
296 
297     }
298     /*Post draw when the children are drawn*/
299     else if(mode == LV_DESIGN_DRAW_POST) {
300         ancestor_design(gauge, mask, mode);
301     }
302 
303     return true;
304 }
305 
306 /**
307  * Signal function of the gauge
308  * @param gauge pointer to a gauge object
309  * @param sign a signal type from lv_signal_t enum
310  * @param param pointer to a signal specific variable
311  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
312  */
lv_gauge_signal(lv_obj_t * gauge,lv_signal_t sign,void * param)313 static lv_res_t lv_gauge_signal(lv_obj_t * gauge, lv_signal_t sign, void * param)
314 {
315     lv_res_t res;
316 
317     /* Include the ancient signal function */
318     res = ancestor_signal(gauge, sign, param);
319     if(res != LV_RES_OK) return res;
320 
321     lv_gauge_ext_t * ext = lv_obj_get_ext_attr(gauge);
322     if(sign == LV_SIGNAL_CLEANUP) {
323         lv_mem_free(ext->values);
324         ext->values = NULL;
325     } else if(sign == LV_SIGNAL_GET_TYPE) {
326         lv_obj_type_t * buf = param;
327         uint8_t i;
328         for(i = 0; i < LV_MAX_ANCESTOR_NUM - 1; i++) { /*Find the last set data*/
329             if(buf->type[i] == NULL) break;
330         }
331         buf->type[i] = "lv_gauge";
332     }
333 
334     return res;
335 }
336 
337 /**
338  * Draw the scale on a gauge
339  * @param gauge pointer to gauge object
340  * @param mask mask of drawing
341  */
lv_gauge_draw_scale(lv_obj_t * gauge,const lv_area_t * mask)342 static void lv_gauge_draw_scale(lv_obj_t * gauge, const lv_area_t * mask)
343 {
344     char scale_txt[16];
345 
346     lv_gauge_ext_t * ext     = lv_obj_get_ext_attr(gauge);
347     const lv_style_t * style = lv_obj_get_style(gauge);
348     lv_opa_t opa_scale       = lv_obj_get_opa_scale(gauge);
349     lv_coord_t r             = lv_obj_get_width(gauge) / 2 - (3 * style->body.padding.left) - style->body.padding.inner;
350     lv_coord_t x_ofs         = lv_obj_get_width(gauge) / 2 + gauge->coords.x1;
351     lv_coord_t y_ofs         = lv_obj_get_height(gauge) / 2 + gauge->coords.y1;
352     int16_t scale_angle      = lv_lmeter_get_scale_angle(gauge);
353     uint16_t label_num       = ext->label_count;
354     int16_t angle_ofs        = 90 + (360 - scale_angle) / 2;
355     int16_t min              = lv_gauge_get_min_value(gauge);
356     int16_t max              = lv_gauge_get_max_value(gauge);
357 
358     uint8_t i;
359     for(i = 0; i < label_num; i++) {
360         /*Calculate the position a scale label*/
361         int16_t angle = (i * scale_angle) / (label_num - 1) + angle_ofs;
362 
363         lv_coord_t y = (int32_t)((int32_t)lv_trigo_sin(angle) * r) / LV_TRIGO_SIN_MAX;
364         y += y_ofs;
365 
366         lv_coord_t x = (int32_t)((int32_t)lv_trigo_sin(angle + 90) * r) / LV_TRIGO_SIN_MAX;
367         x += x_ofs;
368 
369         int16_t scale_act = (int32_t)((int32_t)(max - min) * i) / (label_num - 1);
370         scale_act += min;
371         lv_utils_num_to_str(scale_act, scale_txt);
372 
373         lv_area_t label_cord;
374         lv_point_t label_size;
375         lv_txt_get_size(&label_size, scale_txt, style->text.font, style->text.letter_space, style->text.line_space,
376                         LV_COORD_MAX, LV_TXT_FLAG_NONE);
377 
378         /*Draw the label*/
379         label_cord.x1 = x - label_size.x / 2;
380         label_cord.y1 = y - label_size.y / 2;
381         label_cord.x2 = label_cord.x1 + label_size.x;
382         label_cord.y2 = label_cord.y1 + label_size.y;
383 
384         lv_draw_label(&label_cord, mask, style, opa_scale, scale_txt, LV_TXT_FLAG_NONE, NULL, -1, -1, NULL);
385     }
386 }
387 /**
388  * Draw the needles of a gauge
389  * @param gauge pointer to gauge object
390  * @param mask mask of drawing
391  */
lv_gauge_draw_needle(lv_obj_t * gauge,const lv_area_t * mask)392 static void lv_gauge_draw_needle(lv_obj_t * gauge, const lv_area_t * mask)
393 {
394     lv_style_t style_needle;
395     lv_gauge_ext_t * ext     = lv_obj_get_ext_attr(gauge);
396     const lv_style_t * style = lv_gauge_get_style(gauge, LV_GAUGE_STYLE_MAIN);
397     lv_opa_t opa_scale       = lv_obj_get_opa_scale(gauge);
398 
399     lv_coord_t r      = lv_obj_get_width(gauge) / 2 - style->body.padding.left;
400     lv_coord_t x_ofs  = lv_obj_get_width(gauge) / 2 + gauge->coords.x1;
401     lv_coord_t y_ofs  = lv_obj_get_height(gauge) / 2 + gauge->coords.y1;
402     uint16_t angle    = lv_lmeter_get_scale_angle(gauge);
403     int16_t angle_ofs = 90 + (360 - angle) / 2;
404     int16_t min       = lv_gauge_get_min_value(gauge);
405     int16_t max       = lv_gauge_get_max_value(gauge);
406     lv_point_t p_mid;
407     lv_point_t p_end;
408     lv_point_t p_end_low;
409     lv_point_t p_end_high;
410     uint8_t i;
411 
412     lv_style_copy(&style_needle, style);
413 
414     p_mid.x = x_ofs;
415     p_mid.y = y_ofs;
416     for(i = 0; i < ext->needle_count; i++) {
417         /*Calculate the end point of a needle*/
418         int16_t needle_angle =
419             (ext->values[i] - min) * angle * (1 << LV_GAUGE_INTERPOLATE_SHIFT) / (max - min);
420 
421         int16_t needle_angle_low  = (needle_angle >> LV_GAUGE_INTERPOLATE_SHIFT) + angle_ofs;
422         int16_t needle_angle_high = needle_angle_low + 1;
423 
424         p_end_low.y = (lv_trigo_sin(needle_angle_low) * r) / LV_TRIGO_SIN_MAX + y_ofs;
425         p_end_low.x = (lv_trigo_sin(needle_angle_low + 90) * r) / LV_TRIGO_SIN_MAX + x_ofs;
426 
427         p_end_high.y = (lv_trigo_sin(needle_angle_high) * r) / LV_TRIGO_SIN_MAX + y_ofs;
428         p_end_high.x = (lv_trigo_sin(needle_angle_high + 90) * r) / LV_TRIGO_SIN_MAX + x_ofs;
429 
430         uint16_t rem  = needle_angle & ((1 << LV_GAUGE_INTERPOLATE_SHIFT) - 1);
431         int16_t x_mod = ((LV_MATH_ABS(p_end_high.x - p_end_low.x)) * rem) >> LV_GAUGE_INTERPOLATE_SHIFT;
432         int16_t y_mod = ((LV_MATH_ABS(p_end_high.y - p_end_low.y)) * rem) >> LV_GAUGE_INTERPOLATE_SHIFT;
433 
434         if(p_end_high.x < p_end_low.x) x_mod = -x_mod;
435         if(p_end_high.y < p_end_low.y) y_mod = -y_mod;
436 
437         p_end.x = p_end_low.x + x_mod;
438         p_end.y = p_end_low.y + y_mod;
439 
440         /*Draw the needle with the corresponding color*/
441         if(ext->needle_colors == NULL)
442             style_needle.line.color = LV_GAUGE_DEF_NEEDLE_COLOR;
443         else
444             style_needle.line.color = ext->needle_colors[i];
445 
446         lv_draw_line(&p_mid, &p_end, mask, &style_needle, opa_scale);
447     }
448 
449     /*Draw the needle middle area*/
450     lv_style_t style_neddle_mid;
451     lv_style_copy(&style_neddle_mid, &lv_style_plain);
452     style_neddle_mid.body.main_color = style->body.border.color;
453     style_neddle_mid.body.grad_color = style->body.border.color;
454     style_neddle_mid.body.radius     = LV_RADIUS_CIRCLE;
455 
456     lv_area_t nm_cord;
457     nm_cord.x1 = x_ofs - style->body.radius;
458     nm_cord.y1 = y_ofs - style->body.radius;
459     nm_cord.x2 = x_ofs + style->body.radius;
460     nm_cord.y2 = y_ofs + style->body.radius;
461 
462     lv_draw_rect(&nm_cord, mask, &style_neddle_mid, lv_obj_get_opa_scale(gauge));
463 }
464 
465 #endif
466