1 /**
2  * @file lv_cpicker.c
3  *
4  * From @AloyseTech and @paulpv.
5  */
6 
7 /*********************
8  *      INCLUDES
9  *********************/
10 #include "lv_cpicker.h"
11 #if LV_USE_CPICKER != 0
12 
13 #include "../lv_misc/lv_debug.h"
14 #include "../lv_draw/lv_draw_arc.h"
15 #include "../lv_themes/lv_theme.h"
16 #include "../lv_core/lv_indev.h"
17 #include "../lv_core/lv_refr.h"
18 #include "../lv_misc/lv_math.h"
19 
20 /*********************
21  *      DEFINES
22  *********************/
23 #define LV_OBJX_NAME "lv_cpicker"
24 
25 #ifndef LV_CPICKER_DEF_TYPE
26     #define LV_CPICKER_DEF_TYPE LV_CPICKER_TYPE_DISC
27 #endif
28 
29 #ifndef LV_CPICKER_DEF_HUE
30     #define LV_CPICKER_DEF_HUE 0
31 #endif
32 
33 #ifndef LV_CPICKER_DEF_SATURATION
34     #define LV_CPICKER_DEF_SATURATION 100
35 #endif
36 
37 #ifndef LV_CPICKER_DEF_VALUE
38     #define LV_CPICKER_DEF_VALUE 100
39 #endif
40 
41 #ifndef LV_CPICKER_DEF_HSV
42     #define LV_CPICKER_DEF_HSV ((lv_color_hsv_t){LV_CPICKER_DEF_HUE, LV_CPICKER_DEF_SATURATION, LV_CPICKER_DEF_VALUE})
43 #endif
44 
45 #ifndef LV_CPICKER_DEF_QF /*quantization factor*/
46     #define LV_CPICKER_DEF_QF 3
47 #endif
48 
49 #define TRI_OFFSET 2
50 
51 /* The OUTER_MASK_WIDTH define is required to assist with the placing of a mask over the outer ring of the widget as when the
52  * multicoloured radial lines are calculated for the outer ring of the widget their lengths are jittering because of the
53  * integer based arithmetic. From tests the maximum delta was found to be 2 so the current value is set to 3 to achieve
54  * appropriate masking.
55  */
56 #define OUTER_MASK_WIDTH 3
57 
58 /**********************
59  *      TYPEDEFS
60  **********************/
61 
62 /**********************
63  *  STATIC PROTOTYPES
64  **********************/
65 static lv_design_res_t lv_cpicker_design(lv_obj_t * cpicker, const lv_area_t * clip_area, lv_design_mode_t mode);
66 static lv_res_t lv_cpicker_signal(lv_obj_t * cpicker, lv_signal_t sign, void * param);
67 static lv_style_list_t * lv_cpicker_get_style(lv_obj_t * cpicker, uint8_t part);
68 static bool lv_cpicker_hit(lv_obj_t * cpicker, const lv_point_t * p);
69 
70 static void draw_rect_grad(lv_obj_t * cpicker, const lv_area_t * mask);
71 static void draw_disc_grad(lv_obj_t * cpicker, const lv_area_t * mask);
72 static void draw_knob(lv_obj_t * cpicker, const lv_area_t * mask);
73 static void invalidate_knob(lv_obj_t * cpicker);
74 static lv_area_t get_knob_area(lv_obj_t * cpicker);
75 
76 static void next_color_mode(lv_obj_t * cpicker);
77 static lv_res_t double_click_reset(lv_obj_t * cpicker);
78 static void refr_knob_pos(lv_obj_t * cpicker);
79 static lv_color_t angle_to_mode_color(lv_obj_t * cpicker, uint16_t angle);
80 static uint16_t get_angle(lv_obj_t * cpicker);
81 
82 /**********************
83  *  STATIC VARIABLES
84  **********************/
85 static lv_signal_cb_t ancestor_signal;
86 static lv_design_cb_t ancestor_design;
87 
88 /**********************
89  *      MACROS
90  **********************/
91 
92 /**********************
93  *   GLOBAL FUNCTIONS
94  **********************/
95 
96 /**
97  * Create a color_picker object
98  * @param par pointer to an object, it will be the parent of the new color_picker
99  * @param copy pointer to a color_picker object, if not NULL then the new object will be copied from it
100  * @return pointer to the created color_picker
101  */
lv_cpicker_create(lv_obj_t * par,const lv_obj_t * copy)102 lv_obj_t * lv_cpicker_create(lv_obj_t * par, const lv_obj_t * copy)
103 {
104     LV_LOG_TRACE("color_picker create started");
105 
106     lv_obj_t * cpicker = lv_obj_create(par, copy);
107     LV_ASSERT_MEM(cpicker);
108     if(cpicker == NULL) return NULL;
109 
110     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(cpicker);
111     if(ancestor_design == NULL) ancestor_design = lv_obj_get_design_cb(cpicker);
112 
113     /*Allocate the extended data*/
114     lv_cpicker_ext_t * ext = lv_obj_allocate_ext_attr(cpicker, sizeof(lv_cpicker_ext_t));
115     LV_ASSERT_MEM(ext);
116     if(ext == NULL) {
117         lv_obj_del(cpicker);
118         return NULL;
119     }
120 
121     /*Initialize the allocated 'ext' */
122     ext->type = LV_CPICKER_DEF_TYPE;
123     ext->hsv = LV_CPICKER_DEF_HSV;
124     ext->knob.colored = 1;
125     ext->color_mode = LV_CPICKER_COLOR_MODE_HUE;
126     ext->color_mode_fixed = 0;
127     ext->last_click_time = 0;
128     ext->last_change_time = 0;
129 
130     lv_style_list_init(&ext->knob.style_list);
131 
132     /*The signal and design functions are not copied so set them here*/
133     lv_obj_set_signal_cb(cpicker, lv_cpicker_signal);
134     lv_obj_set_design_cb(cpicker, lv_cpicker_design);
135 
136     /*If no copy do the basic initialization*/
137     if(copy == NULL) {
138         lv_obj_set_size(cpicker, LV_DPI * 2, LV_DPI * 2);
139         lv_obj_add_protect(cpicker, LV_PROTECT_PRESS_LOST);
140         lv_obj_set_adv_hittest(cpicker, true);
141         lv_theme_apply(cpicker, LV_THEME_CPICKER);
142     }
143     /*Copy 'copy'*/
144     else {
145         lv_cpicker_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
146         ext->type = copy_ext->type;
147         ext->color_mode = copy_ext->color_mode;
148         ext->color_mode_fixed = copy_ext->color_mode_fixed;
149         ext->hsv = copy_ext->hsv;
150         ext->knob.colored = copy_ext->knob.colored;
151 
152         lv_style_list_copy(&ext->knob.style_list, &copy_ext->knob.style_list);
153         /*Refresh the style with new signal function*/
154         lv_obj_refresh_style(cpicker, LV_OBJ_PART_ALL, LV_STYLE_PROP_ALL);
155     }
156     refr_knob_pos(cpicker);
157 
158     LV_LOG_INFO("color_picker created");
159 
160     return cpicker;
161 }
162 
163 /*=====================
164  * Setter functions
165  *====================*/
166 
167 /**
168  * Set a new type for a cpicker
169  * @param cpicker pointer to a cpicker object
170  * @param type new type of the cpicker (from 'lv_cpicker_type_t' enum)
171  */
lv_cpicker_set_type(lv_obj_t * cpicker,lv_cpicker_type_t type)172 void lv_cpicker_set_type(lv_obj_t * cpicker, lv_cpicker_type_t type)
173 {
174     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
175 
176     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
177     if(ext->type == type) return;
178 
179     ext->type = type;
180     lv_obj_refresh_ext_draw_pad(cpicker);
181     refr_knob_pos(cpicker);
182 
183     lv_obj_invalidate(cpicker);
184 }
185 
186 /**
187  * Set the current hue of a colorpicker.
188  * @param cpicker pointer to colorpicker object
189  * @param hue current selected hue [0..360]
190  * @return true if changed, otherwise false
191  */
lv_cpicker_set_hue(lv_obj_t * cpicker,uint16_t hue)192 bool lv_cpicker_set_hue(lv_obj_t * cpicker, uint16_t hue)
193 {
194     lv_color_hsv_t hsv = lv_cpicker_get_hsv(cpicker);
195     hsv.h = hue;
196     return lv_cpicker_set_hsv(cpicker, hsv);
197 }
198 
199 /**
200  * Set the current saturation of a colorpicker.
201  * @param cpicker pointer to colorpicker object
202  * @param saturation current selected saturation [0..100]
203  * @return true if changed, otherwise false
204  */
lv_cpicker_set_saturation(lv_obj_t * cpicker,uint8_t saturation)205 bool lv_cpicker_set_saturation(lv_obj_t * cpicker, uint8_t saturation)
206 {
207     lv_color_hsv_t hsv = lv_cpicker_get_hsv(cpicker);
208     hsv.s = saturation;
209     return lv_cpicker_set_hsv(cpicker, hsv);
210 }
211 
212 /**
213  * Set the current value of a colorpicker.
214  * @param cpicker pointer to colorpicker object
215  * @param val current selected value [0..100]
216  * @return true if changed, otherwise false
217  */
lv_cpicker_set_value(lv_obj_t * cpicker,uint8_t val)218 bool lv_cpicker_set_value(lv_obj_t * cpicker, uint8_t val)
219 {
220     lv_color_hsv_t hsv = lv_cpicker_get_hsv(cpicker);
221     hsv.v = val;
222     return lv_cpicker_set_hsv(cpicker, hsv);
223 }
224 
225 /**
226  * Set the current hsv of a colorpicker.
227  * @param cpicker pointer to colorpicker object
228  * @param color current selected hsv
229  * @return true if changed, otherwise false
230  */
lv_cpicker_set_hsv(lv_obj_t * cpicker,lv_color_hsv_t hsv)231 bool lv_cpicker_set_hsv(lv_obj_t * cpicker, lv_color_hsv_t hsv)
232 {
233     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
234 
235     if(hsv.h > 360) hsv.h %= 360;
236     if(hsv.s > 100) hsv.s = 100;
237     if(hsv.v > 100) hsv.v = 100;
238 
239     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
240 
241     if(ext->hsv.h == hsv.h && ext->hsv.s == hsv.s && ext->hsv.v == hsv.v) return false;
242 
243     ext->hsv = hsv;
244 
245     refr_knob_pos(cpicker);
246 
247     lv_obj_invalidate(cpicker);
248 
249     return true;
250 }
251 
252 /**
253  * Set the current color of a colorpicker.
254  * @param cpicker pointer to colorpicker object
255  * @param color current selected color
256  * @return true if changed, otherwise false
257  */
lv_cpicker_set_color(lv_obj_t * cpicker,lv_color_t color)258 bool lv_cpicker_set_color(lv_obj_t * cpicker, lv_color_t color)
259 {
260     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
261 
262     lv_color32_t c32;
263     c32.full = lv_color_to32(color);
264 
265     return lv_cpicker_set_hsv(cpicker,
266                               lv_color_rgb_to_hsv(c32.ch.red, c32.ch.green, c32.ch.blue));
267 }
268 
269 /**
270  * Set the current color mode.
271  * @param cpicker pointer to colorpicker object
272  * @param mode color mode (hue/sat/val)
273  */
lv_cpicker_set_color_mode(lv_obj_t * cpicker,lv_cpicker_color_mode_t mode)274 void lv_cpicker_set_color_mode(lv_obj_t * cpicker, lv_cpicker_color_mode_t mode)
275 {
276     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
277 
278     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
279 
280     ext->color_mode = mode;
281     refr_knob_pos(cpicker);
282     lv_obj_invalidate(cpicker);
283 }
284 
285 /**
286  * Set if the color mode is changed on long press on center
287  * @param cpicker pointer to colorpicker object
288  * @param fixed color mode cannot be changed on long press
289  */
lv_cpicker_set_color_mode_fixed(lv_obj_t * cpicker,bool fixed)290 void lv_cpicker_set_color_mode_fixed(lv_obj_t * cpicker, bool fixed)
291 {
292     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
293 
294     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
295 
296     ext->color_mode_fixed = fixed;
297 }
298 
299 /**
300  * Make the knob to be colored to the current color
301  * @param cpicker pointer to colorpicker object
302  * @param en true: color the knob; false: not color the knob
303  */
lv_cpicker_set_knob_colored(lv_obj_t * cpicker,bool en)304 void lv_cpicker_set_knob_colored(lv_obj_t * cpicker, bool en)
305 {
306     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
307 
308     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
309     ext->knob.colored = en ? 1 : 0;
310     invalidate_knob(cpicker);
311 }
312 
313 /*=====================
314  * Getter functions
315  *====================*/
316 
317 /**
318  * Get the current color mode.
319  * @param cpicker pointer to colorpicker object
320  * @return color mode (hue/sat/val)
321  */
lv_cpicker_get_color_mode(lv_obj_t * cpicker)322 lv_cpicker_color_mode_t lv_cpicker_get_color_mode(lv_obj_t * cpicker)
323 {
324     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
325 
326     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
327 
328     return ext->color_mode;
329 }
330 
331 /**
332  * Get if the color mode is changed on long press on center
333  * @param cpicker pointer to colorpicker object
334  * @return mode cannot be changed on long press
335  */
lv_cpicker_get_color_mode_fixed(lv_obj_t * cpicker)336 bool lv_cpicker_get_color_mode_fixed(lv_obj_t * cpicker)
337 {
338     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
339 
340     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
341 
342     return ext->color_mode_fixed;
343 }
344 
345 /**
346  * Get the current selected hue of a colorpicker.
347  * @param cpicker pointer to colorpicker object
348  * @return hue current selected hue
349  */
lv_cpicker_get_hue(lv_obj_t * cpicker)350 uint16_t lv_cpicker_get_hue(lv_obj_t * cpicker)
351 {
352     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
353 
354     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
355 
356     return ext->hsv.h;
357 }
358 
359 /**
360  * Get the current selected saturation of a colorpicker.
361  * @param cpicker pointer to colorpicker object
362  * @return current selected saturation
363  */
lv_cpicker_get_saturation(lv_obj_t * cpicker)364 uint8_t lv_cpicker_get_saturation(lv_obj_t * cpicker)
365 {
366     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
367 
368     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
369 
370     return ext->hsv.s;
371 }
372 
373 /**
374  * Get the current selected hue of a colorpicker.
375  * @param cpicker pointer to colorpicker object
376  * @return current selected value
377  */
lv_cpicker_get_value(lv_obj_t * cpicker)378 uint8_t lv_cpicker_get_value(lv_obj_t * cpicker)
379 {
380     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
381 
382     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
383 
384     return ext->hsv.v;
385 }
386 
387 /**
388  * Get the current selected hsv of a colorpicker.
389  * @param cpicker pointer to colorpicker object
390  * @return current selected hsv
391  */
lv_cpicker_get_hsv(lv_obj_t * cpicker)392 lv_color_hsv_t lv_cpicker_get_hsv(lv_obj_t * cpicker)
393 {
394     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
395 
396     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
397 
398     return ext->hsv;
399 }
400 
401 /**
402  * Get the current selected color of a colorpicker.
403  * @param cpicker pointer to colorpicker object
404  * @return color current selected color
405  */
lv_cpicker_get_color(lv_obj_t * cpicker)406 lv_color_t lv_cpicker_get_color(lv_obj_t * cpicker)
407 {
408     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
409 
410     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
411 
412     return lv_color_hsv_to_rgb(ext->hsv.h, ext->hsv.s, ext->hsv.v);
413 }
414 
415 /**
416  * Whether the knob is colored to the current color or not
417  * @param cpicker pointer to color picker object
418  * @return true: color the knob; false: not color the knob
419  */
lv_cpicker_get_knob_colored(lv_obj_t * cpicker)420 bool lv_cpicker_get_knob_colored(lv_obj_t * cpicker)
421 {
422     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
423 
424     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
425 
426     return ext->knob.colored ? true : false;
427 }
428 
429 /*=====================
430  * Other functions
431  *====================*/
432 
433 /**********************
434  *   STATIC FUNCTIONS
435  **********************/
436 
437 
438 /**
439  * Handle the drawing related tasks of the color_picker
440  * @param cpicker pointer to an object
441  * @param mask the object will be drawn only in this area
442  * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
443  *                                  (return 'true' if yes)
444  *             LV_DESIGN_DRAW: draw the object (always return 'true')
445  *             LV_DESIGN_DRAW_POST: drawing after every children are drawn
446  * @return return an element of `lv_design_res_t`
447  */
lv_cpicker_design(lv_obj_t * cpicker,const lv_area_t * clip_area,lv_design_mode_t mode)448 static lv_design_res_t lv_cpicker_design(lv_obj_t * cpicker, const lv_area_t * clip_area, lv_design_mode_t mode)
449 {
450     /*Return false if the object is not covers the mask_p area*/
451     if(mode == LV_DESIGN_COVER_CHK)  {
452         return LV_DESIGN_RES_NOT_COVER;
453     }
454     /*Draw the object*/
455     else if(mode == LV_DESIGN_DRAW_MAIN) {
456         lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
457 
458         if(ext->type == LV_CPICKER_TYPE_DISC) {
459             draw_disc_grad(cpicker, clip_area);
460         }
461         else if(ext->type == LV_CPICKER_TYPE_RECT) {
462             draw_rect_grad(cpicker, clip_area);
463         }
464 
465         draw_knob(cpicker, clip_area);
466     }
467     /*Post draw when the children are drawn*/
468     else if(mode == LV_DESIGN_DRAW_POST) {
469     }
470 
471     return LV_DESIGN_RES_OK;
472 }
473 
draw_disc_grad(lv_obj_t * cpicker,const lv_area_t * mask)474 static void draw_disc_grad(lv_obj_t * cpicker, const lv_area_t * mask)
475 {
476     lv_coord_t w = lv_obj_get_width(cpicker);
477     lv_coord_t h = lv_obj_get_height(cpicker);
478     lv_coord_t cx = cpicker->coords.x1 + w / 2;
479     lv_coord_t cy = cpicker->coords.y1 + h / 2;
480     lv_coord_t r = w / 2;
481 
482     lv_draw_line_dsc_t line_dsc;
483     lv_draw_line_dsc_init(&line_dsc);
484     lv_obj_init_draw_line_dsc(cpicker, LV_CPICKER_PART_MAIN, &line_dsc);
485 
486     line_dsc.width = (r * 628 / (360 / LV_CPICKER_DEF_QF)) / 100;
487     line_dsc.width += 2;
488     uint16_t i;
489     lv_coord_t cir_w = lv_obj_get_style_scale_width(cpicker, LV_CPICKER_PART_MAIN);
490 
491     /* Mask outer ring of widget to tidy up ragged edges of lines while drawing outer ring */
492     lv_area_t mask_area_out;
493     lv_area_copy(&mask_area_out, &cpicker->coords);
494     mask_area_out.x1 += OUTER_MASK_WIDTH;
495     mask_area_out.x2 -= OUTER_MASK_WIDTH;
496     mask_area_out.y1 += OUTER_MASK_WIDTH;
497     mask_area_out.y2 -= OUTER_MASK_WIDTH;
498     lv_draw_mask_radius_param_t mask_out_param;
499     lv_draw_mask_radius_init(&mask_out_param, &mask_area_out, LV_RADIUS_CIRCLE, false);
500     int16_t mask_out_id = lv_draw_mask_add(&mask_out_param, 0);
501 
502     /* The inner line ends will be masked out.
503      * So make lines a little bit longer because the masking makes a more even result */
504     lv_coord_t cir_w_extra = cir_w + line_dsc.width;
505 
506     for(i = 0; i <= 360; i += LV_CPICKER_DEF_QF) {
507         line_dsc.color = angle_to_mode_color(cpicker, i);
508 
509         lv_point_t p[2];
510         p[0].x = cx + (r * _lv_trigo_sin(i) >> LV_TRIGO_SHIFT);
511         p[0].y = cy + (r * _lv_trigo_sin(i + 90) >> LV_TRIGO_SHIFT);
512         p[1].x = cx + ((r - cir_w_extra) * _lv_trigo_sin(i) >> LV_TRIGO_SHIFT);
513         p[1].y = cy + ((r - cir_w_extra) * _lv_trigo_sin(i + 90) >> LV_TRIGO_SHIFT);
514 
515         lv_draw_line(&p[0], &p[1], mask, &line_dsc);
516     }
517     /* Now remove mask to continue with inner part */
518     lv_draw_mask_remove_id(mask_out_id);
519 
520     /*Mask out the inner area*/
521     lv_draw_rect_dsc_t bg_dsc;
522     lv_draw_rect_dsc_init(&bg_dsc);
523     lv_obj_init_draw_rect_dsc(cpicker, LV_CPICKER_PART_MAIN, &bg_dsc);
524     bg_dsc.radius = LV_RADIUS_CIRCLE;
525 
526     lv_area_t area_mid;
527     lv_area_copy(&area_mid, &cpicker->coords);
528     area_mid.x1 += cir_w;
529     area_mid.y1 += cir_w;
530     area_mid.x2 -= cir_w;
531     area_mid.y2 -= cir_w;
532 
533     lv_draw_rect(&area_mid, mask, &bg_dsc);
534 
535     lv_style_int_t inner = lv_obj_get_style_pad_inner(cpicker, LV_CPICKER_PART_MAIN);
536     lv_color_t color = lv_cpicker_get_color(cpicker);
537     bg_dsc.bg_color = color;
538     area_mid.x1 += inner;
539     area_mid.y1 += inner;
540     area_mid.x2 -= inner;
541     area_mid.y2 -= inner;
542 
543     lv_draw_rect(&area_mid, mask, &bg_dsc);
544 }
545 
draw_rect_grad(lv_obj_t * cpicker,const lv_area_t * mask)546 static void draw_rect_grad(lv_obj_t * cpicker, const lv_area_t * mask)
547 {
548     lv_draw_rect_dsc_t bg_dsc;
549     lv_draw_rect_dsc_init(&bg_dsc);
550     lv_obj_init_draw_rect_dsc(cpicker, LV_CPICKER_PART_MAIN, &bg_dsc);
551 
552     lv_area_t grad_area;
553     lv_obj_get_coords(cpicker, &grad_area);
554 
555     if(bg_dsc.radius) {
556         lv_coord_t h = lv_obj_get_height(cpicker);
557         lv_coord_t r = bg_dsc.radius;
558         if(r > h / 2) r = h / 2;
559         /*Make the gradient area smaller with a half circle on both ends*/
560         grad_area.x1 += r;
561         grad_area.x2 -= r;
562 
563         /*Draw the left rounded end*/
564         lv_area_t rounded_edge_area;
565         lv_obj_get_coords(cpicker, &rounded_edge_area);
566         rounded_edge_area.x2 = rounded_edge_area.x1 + 2 * r;
567 
568         bg_dsc.bg_color = angle_to_mode_color(cpicker, 0);
569 
570         lv_draw_rect(&rounded_edge_area, mask, &bg_dsc);
571 
572         /*Draw the right rounded end*/
573         lv_obj_get_coords(cpicker, &rounded_edge_area);
574         rounded_edge_area.x1 = rounded_edge_area.x2 - 2 * r;
575 
576         bg_dsc.bg_color = angle_to_mode_color(cpicker, 359);
577 
578         lv_draw_rect(&rounded_edge_area, mask, &bg_dsc);
579     }
580 
581     lv_coord_t grad_w = lv_area_get_width(&grad_area);
582     uint16_t i_step = LV_MATH_MAX(LV_CPICKER_DEF_QF, 360 / grad_w);
583     bg_dsc.radius = 0;
584     bg_dsc.border_width = 0;
585     bg_dsc.shadow_width = 0;
586 
587     uint16_t i;
588     for(i = 0; i < 360; i += i_step) {
589         bg_dsc.bg_color = angle_to_mode_color(cpicker, i);
590 
591         /*the following attribute might need changing between index to add border, shadow, radius etc*/
592         lv_area_t rect_area;
593 
594         /*scale angle (hue/sat/val) to linear coordinate*/
595         lv_coord_t xi = (i * grad_w) / 360;
596         lv_coord_t xi2 = ((i + i_step) * grad_w) / 360;
597 
598         rect_area.x1 = LV_MATH_MIN(grad_area.x1 + xi, grad_area.x1 + grad_w - i_step);
599         rect_area.y1 = grad_area.y1;
600         rect_area.x2 = LV_MATH_MIN(grad_area.x1 + xi2, grad_area.x1 + grad_w - i_step);
601         rect_area.y2 = grad_area.y2;
602 
603         lv_draw_rect(&rect_area, mask, &bg_dsc);
604     }
605 }
606 
draw_knob(lv_obj_t * cpicker,const lv_area_t * mask)607 static void draw_knob(lv_obj_t * cpicker, const lv_area_t * mask)
608 {
609     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
610 
611     lv_draw_rect_dsc_t cir_dsc;
612     lv_draw_rect_dsc_init(&cir_dsc);
613     lv_obj_init_draw_rect_dsc(cpicker, LV_CPICKER_PART_KNOB, &cir_dsc);
614 
615     cir_dsc.radius = LV_RADIUS_CIRCLE;
616 
617     if(ext->knob.colored) {
618         cir_dsc.bg_color = lv_cpicker_get_color(cpicker);
619     }
620 
621     lv_area_t knob_area = get_knob_area(cpicker);
622 
623     lv_draw_rect(&knob_area, mask, &cir_dsc);
624 }
625 
invalidate_knob(lv_obj_t * cpicker)626 static void invalidate_knob(lv_obj_t * cpicker)
627 {
628     lv_area_t knob_area = get_knob_area(cpicker);
629 
630     lv_obj_invalidate_area(cpicker, &knob_area);
631 }
632 
get_knob_area(lv_obj_t * cpicker)633 static lv_area_t get_knob_area(lv_obj_t * cpicker)
634 {
635     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
636 
637     /*Get knob's radius*/
638     uint16_t r = 0;
639     if(ext->type == LV_CPICKER_TYPE_DISC) {
640         r = lv_obj_get_style_scale_width(cpicker, LV_CPICKER_PART_MAIN) / 2;
641     }
642     else if(ext->type == LV_CPICKER_TYPE_RECT) {
643         lv_coord_t h = lv_obj_get_height(cpicker);
644         r = h / 2;
645     }
646 
647     lv_style_int_t left = lv_obj_get_style_pad_left(cpicker, LV_CPICKER_PART_KNOB);
648     lv_style_int_t right = lv_obj_get_style_pad_right(cpicker, LV_CPICKER_PART_KNOB);
649     lv_style_int_t top = lv_obj_get_style_pad_top(cpicker, LV_CPICKER_PART_KNOB);
650     lv_style_int_t bottom = lv_obj_get_style_pad_bottom(cpicker, LV_CPICKER_PART_KNOB);
651 
652     lv_area_t knob_area;
653     knob_area.x1 = cpicker->coords.x1 + ext->knob.pos.x - r - left;
654     knob_area.y1 = cpicker->coords.y1 + ext->knob.pos.y - r - right;
655     knob_area.x2 = cpicker->coords.x1 + ext->knob.pos.x + r + top;
656     knob_area.y2 = cpicker->coords.y1 + ext->knob.pos.y + r + bottom;
657 
658     return knob_area;
659 }
660 
661 /**
662  * Signal function of the color_picker
663  * @param cpicker pointer to a color_picker object
664  * @param sign a signal type from lv_signal_t enum
665  * @param param pointer to a signal specific variable
666  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
667  */
lv_cpicker_signal(lv_obj_t * cpicker,lv_signal_t sign,void * param)668 static lv_res_t lv_cpicker_signal(lv_obj_t * cpicker, lv_signal_t sign, void * param)
669 {
670     /* Include the ancient signal function */
671     lv_res_t res;
672 
673     if(sign == LV_SIGNAL_GET_STYLE) {
674         lv_get_style_info_t * info = param;
675         info->result = lv_cpicker_get_style(cpicker, info->part);
676         if(info->result != NULL) return LV_RES_OK;
677         else return ancestor_signal(cpicker, sign, param);
678     }
679 
680     res = ancestor_signal(cpicker, sign, param);
681     if(res != LV_RES_OK) return res;
682     if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
683 
684     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
685 
686     if(sign == LV_SIGNAL_CLEANUP) {
687         lv_obj_clean_style_list(cpicker, LV_CPICKER_PART_KNOB);
688     }
689     else if(sign == LV_SIGNAL_REFR_EXT_DRAW_PAD) {
690         lv_style_int_t left = lv_obj_get_style_pad_left(cpicker, LV_CPICKER_PART_KNOB);
691         lv_style_int_t right = lv_obj_get_style_pad_right(cpicker, LV_CPICKER_PART_KNOB);
692         lv_style_int_t top = lv_obj_get_style_pad_top(cpicker, LV_CPICKER_PART_KNOB);
693         lv_style_int_t bottom = lv_obj_get_style_pad_bottom(cpicker, LV_CPICKER_PART_KNOB);
694 
695         lv_coord_t knob_pad = LV_MATH_MAX4(left, right, top, bottom) + 2;
696 
697         if(ext->type == LV_CPICKER_TYPE_DISC) {
698             cpicker->ext_draw_pad = LV_MATH_MAX(cpicker->ext_draw_pad, knob_pad);
699         }
700         else {
701             knob_pad += lv_obj_get_height(cpicker) / 2;
702             cpicker->ext_draw_pad = LV_MATH_MAX(cpicker->ext_draw_pad, knob_pad);
703         }
704     }
705     else if(sign == LV_SIGNAL_COORD_CHG) {
706         /*Refresh extended draw area to make knob visible*/
707         if(lv_obj_get_width(cpicker) != lv_area_get_width(param) ||
708            lv_obj_get_height(cpicker) != lv_area_get_height(param)) {
709             lv_obj_refresh_ext_draw_pad(cpicker);
710             refr_knob_pos(cpicker);
711             lv_obj_invalidate(cpicker);
712         }
713     }
714     else if(sign == LV_SIGNAL_STYLE_CHG) {
715         /*Refresh extended draw area to make knob visible*/
716         lv_obj_refresh_ext_draw_pad(cpicker);
717         refr_knob_pos(cpicker);
718         lv_obj_invalidate(cpicker);
719     }
720     else if(sign == LV_SIGNAL_CONTROL) {
721 #if LV_USE_GROUP
722         uint32_t c = *((uint32_t *)param); /*uint32_t because can be UTF-8*/
723 
724         if(c == LV_KEY_RIGHT || c == LV_KEY_UP) {
725             lv_color_hsv_t hsv_cur;
726             hsv_cur = ext->hsv;
727 
728             switch(ext->color_mode) {
729                 case LV_CPICKER_COLOR_MODE_HUE:
730                     hsv_cur.h = (ext->hsv.h + 1) % 360;
731                     break;
732                 case LV_CPICKER_COLOR_MODE_SATURATION:
733                     hsv_cur.s = (ext->hsv.s + 1) % 100;
734                     break;
735                 case LV_CPICKER_COLOR_MODE_VALUE:
736                     hsv_cur.v = (ext->hsv.v + 1) % 100;
737                     break;
738             }
739 
740             if(lv_cpicker_set_hsv(cpicker, hsv_cur)) {
741                 res = lv_event_send(cpicker, LV_EVENT_VALUE_CHANGED, NULL);
742                 if(res != LV_RES_OK) return res;
743             }
744         }
745         else if(c == LV_KEY_LEFT || c == LV_KEY_DOWN) {
746             lv_color_hsv_t hsv_cur;
747             hsv_cur = ext->hsv;
748 
749             switch(ext->color_mode) {
750                 case LV_CPICKER_COLOR_MODE_HUE:
751                     hsv_cur.h = ext->hsv.h > 0 ? (ext->hsv.h - 1) : 360;
752                     break;
753                 case LV_CPICKER_COLOR_MODE_SATURATION:
754                     hsv_cur.s = ext->hsv.s > 0 ? (ext->hsv.s - 1) : 100;
755                     break;
756                 case LV_CPICKER_COLOR_MODE_VALUE:
757                     hsv_cur.v = ext->hsv.v > 0 ? (ext->hsv.v - 1) : 100;
758                     break;
759             }
760 
761             if(lv_cpicker_set_hsv(cpicker, hsv_cur)) {
762                 res = lv_event_send(cpicker, LV_EVENT_VALUE_CHANGED, NULL);
763                 if(res != LV_RES_OK) return res;
764             }
765         }
766 #endif
767     }
768     else if(sign == LV_SIGNAL_PRESSED) {
769         ext->last_change_time = lv_tick_get();
770         lv_indev_get_point(lv_indev_get_act(), &ext->last_press_point);
771         res = double_click_reset(cpicker);
772         if(res != LV_RES_OK) return res;
773     }
774     else if(sign == LV_SIGNAL_PRESSING) {
775         lv_indev_t * indev = lv_indev_get_act();
776         if(indev == NULL) return res;
777 
778         lv_indev_type_t indev_type = lv_indev_get_type(indev);
779         lv_point_t p;
780         if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) {
781             p.x = cpicker->coords.x1 + lv_obj_get_width(cpicker) / 2;
782             p.y = cpicker->coords.y1 + lv_obj_get_height(cpicker) / 2;
783         }
784         else {
785             lv_indev_get_point(indev, &p);
786         }
787 
788         if((LV_MATH_ABS(p.x - ext->last_press_point.x) > indev->driver.drag_limit / 2) ||
789            (LV_MATH_ABS(p.y - ext->last_press_point.y) > indev->driver.drag_limit / 2)) {
790             ext->last_change_time = lv_tick_get();
791             ext->last_press_point.x = p.x;
792             ext->last_press_point.y = p.y;
793         }
794 
795         p.x -= cpicker->coords.x1;
796         p.y -= cpicker->coords.y1;
797 
798         /*Ignore pressing in the inner area*/
799         uint16_t w = lv_obj_get_width(cpicker);
800 
801         int16_t angle = 0;
802 
803         if(ext->type == LV_CPICKER_TYPE_RECT) {
804             /*If pressed long enough without change go to next color mode*/
805             uint32_t diff = lv_tick_elaps(ext->last_change_time);
806             if(diff > (uint32_t)indev->driver.long_press_time * 2 && !ext->color_mode_fixed) {
807                 next_color_mode(cpicker);
808                 lv_indev_wait_release(lv_indev_get_act());
809                 return res;
810             }
811 
812             angle = (p.x * 360) / w;
813             if(angle < 0) angle = 0;
814             if(angle >= 360) angle = 359;
815 
816         }
817         else if(ext->type == LV_CPICKER_TYPE_DISC) {
818             lv_style_int_t scale_w = lv_obj_get_style_scale_width(cpicker, LV_CPICKER_PART_MAIN);
819 
820             lv_coord_t r_in = w / 2;
821             p.x -= r_in;
822             p.y -= r_in;
823             bool on_ring = true;
824             r_in -= scale_w;
825             if(r_in > LV_DPI / 2) {
826                 lv_style_int_t inner = lv_obj_get_style_pad_inner(cpicker, LV_CPICKER_PART_MAIN);
827                 r_in -= inner;
828 
829                 if(r_in < LV_DPI / 2) r_in = LV_DPI / 2;
830             }
831 
832             if(p.x * p.x + p.y * p.y < r_in * r_in) {
833                 on_ring = false;
834             }
835 
836             /*If the inner area is being pressed, go to the next color mode on long press*/
837             uint32_t diff = lv_tick_elaps(ext->last_change_time);
838             if(!on_ring && diff > indev->driver.long_press_time && !ext->color_mode_fixed) {
839                 next_color_mode(cpicker);
840                 lv_indev_wait_release(lv_indev_get_act());
841                 return res;
842             }
843 
844             /*Set the angle only if pressed on the ring*/
845             if(!on_ring) return res;
846 
847             angle = _lv_atan2(p.x, p.y) % 360;
848         }
849 
850         lv_color_hsv_t hsv_cur;
851         hsv_cur = ext->hsv;
852 
853         switch(ext->color_mode) {
854             case LV_CPICKER_COLOR_MODE_HUE:
855                 hsv_cur.h = angle;
856                 break;
857             case LV_CPICKER_COLOR_MODE_SATURATION:
858                 hsv_cur.s = (angle * 100) / 360;
859                 break;
860             case LV_CPICKER_COLOR_MODE_VALUE:
861                 hsv_cur.v = (angle * 100) / 360;
862                 break;
863         }
864 
865         if(lv_cpicker_set_hsv(cpicker, hsv_cur)) {
866             res = lv_event_send(cpicker, LV_EVENT_VALUE_CHANGED, NULL);
867             if(res != LV_RES_OK) return res;
868         }
869     }
870     else if(sign == LV_SIGNAL_HIT_TEST) {
871         lv_hit_test_info_t * info = param;
872         info->result = lv_cpicker_hit(cpicker, info->point);
873     }
874 
875     return res;
876 }
877 
878 
879 /**
880  * Get the style_list descriptor of a part of the object
881  * @param cpicker pointer the object
882  * @param part the part of the cpicker. (LV_PAGE_CPICKER_...)
883  * @return pointer to the style_list descriptor of the specified part
884  */
lv_cpicker_get_style(lv_obj_t * cpicker,uint8_t part)885 static lv_style_list_t * lv_cpicker_get_style(lv_obj_t * cpicker, uint8_t part)
886 {
887     LV_ASSERT_OBJ(cpicker, LV_OBJX_NAME);
888 
889     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
890     lv_style_list_t * style_dsc_p;
891 
892     switch(part) {
893         case LV_CPICKER_PART_MAIN :
894             style_dsc_p = &cpicker->style_list;
895             break;
896         case LV_CPICKER_PART_KNOB:
897             style_dsc_p = &ext->knob.style_list;
898             break;
899         default:
900             style_dsc_p = NULL;
901     }
902 
903     return style_dsc_p;
904 }
905 
lv_cpicker_hit(lv_obj_t * cpicker,const lv_point_t * p)906 static bool lv_cpicker_hit(lv_obj_t * cpicker, const lv_point_t * p)
907 {
908     bool is_point_on_coords = lv_obj_is_point_on_coords(cpicker, p);
909     if(!is_point_on_coords)
910         return false;
911 
912     lv_cpicker_ext_t * ext = (lv_cpicker_ext_t *)lv_obj_get_ext_attr(cpicker);
913     if(ext->type == LV_CPICKER_TYPE_RECT)
914         return true;
915 
916 
917     /*Valid clicks can be only in the circle*/
918     if(_lv_area_is_point_on(&cpicker->coords, p, LV_RADIUS_CIRCLE)) return true;
919     else return false;
920 }
921 
next_color_mode(lv_obj_t * cpicker)922 static void next_color_mode(lv_obj_t * cpicker)
923 {
924     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
925     ext->color_mode = (ext->color_mode + 1) % 3;
926     refr_knob_pos(cpicker);
927     lv_obj_invalidate(cpicker);
928 }
929 
refr_knob_pos(lv_obj_t * cpicker)930 static void refr_knob_pos(lv_obj_t * cpicker)
931 {
932     invalidate_knob(cpicker);
933 
934     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
935     lv_coord_t w = lv_obj_get_width(cpicker);
936     lv_coord_t h = lv_obj_get_height(cpicker);
937 
938     if(ext->type == LV_CPICKER_TYPE_RECT) {
939         lv_coord_t ind_pos = 0;
940         switch(ext->color_mode) {
941             case LV_CPICKER_COLOR_MODE_HUE:
942                 ind_pos += (ext->hsv.h * w) / 360;
943                 break;
944             case LV_CPICKER_COLOR_MODE_SATURATION:
945                 ind_pos += (ext->hsv.s * w) / 100;
946                 break;
947             case LV_CPICKER_COLOR_MODE_VALUE:
948                 ind_pos += (ext->hsv.v * w) / 100;
949                 break;
950         }
951 
952         ext->knob.pos.x = ind_pos;
953         ext->knob.pos.y = h / 2;
954     }
955     else if(ext->type == LV_CPICKER_TYPE_DISC) {
956         lv_style_int_t scale_w = lv_obj_get_style_scale_width(cpicker, LV_CPICKER_PART_MAIN);
957         lv_coord_t r = (w - scale_w) / 2;
958         uint16_t angle = get_angle(cpicker);
959         ext->knob.pos.x = (((int32_t)r * _lv_trigo_sin(angle)) >> LV_TRIGO_SHIFT);
960         ext->knob.pos.y = (((int32_t)r * _lv_trigo_sin(angle + 90)) >> LV_TRIGO_SHIFT);
961         ext->knob.pos.x = ext->knob.pos.x + w / 2;
962         ext->knob.pos.y = ext->knob.pos.y + h / 2;
963     }
964 
965     invalidate_knob(cpicker);
966 }
967 
double_click_reset(lv_obj_t * cpicker)968 static lv_res_t double_click_reset(lv_obj_t * cpicker)
969 {
970     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
971     lv_indev_t * indev = lv_indev_get_act();
972     /*Double clicked? Use long press time as double click time out*/
973     if(lv_tick_elaps(ext->last_click_time) < indev->driver.long_press_time) {
974         lv_color_hsv_t hsv_cur;
975         hsv_cur = ext->hsv;
976 
977         switch(ext->color_mode) {
978             case LV_CPICKER_COLOR_MODE_HUE:
979                 hsv_cur.h = LV_CPICKER_DEF_HUE;
980                 break;
981             case LV_CPICKER_COLOR_MODE_SATURATION:
982                 hsv_cur.s = LV_CPICKER_DEF_SATURATION;
983                 break;
984             case LV_CPICKER_COLOR_MODE_VALUE:
985                 hsv_cur.v = LV_CPICKER_DEF_VALUE;
986                 break;
987         }
988 
989         lv_indev_wait_release(indev);
990 
991         if(lv_cpicker_set_hsv(cpicker, hsv_cur)) {
992             lv_res_t res = lv_event_send(cpicker, LV_EVENT_VALUE_CHANGED, NULL);
993             if(res != LV_RES_OK) return res;
994         }
995     }
996     ext->last_click_time = lv_tick_get();
997 
998     return LV_RES_OK;
999 }
1000 
angle_to_mode_color(lv_obj_t * cpicker,uint16_t angle)1001 static lv_color_t angle_to_mode_color(lv_obj_t * cpicker, uint16_t angle)
1002 {
1003     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
1004     lv_color_t color;
1005     switch(ext->color_mode) {
1006         default:
1007         case LV_CPICKER_COLOR_MODE_HUE:
1008             color = lv_color_hsv_to_rgb(angle % 360, ext->hsv.s, ext->hsv.v);
1009             break;
1010         case LV_CPICKER_COLOR_MODE_SATURATION:
1011             color = lv_color_hsv_to_rgb(ext->hsv.h, ((angle % 360) * 100) / 360, ext->hsv.v);
1012             break;
1013         case LV_CPICKER_COLOR_MODE_VALUE:
1014             color = lv_color_hsv_to_rgb(ext->hsv.h, ext->hsv.s, ((angle % 360) * 100) / 360);
1015             break;
1016     }
1017     return color;
1018 }
1019 
get_angle(lv_obj_t * cpicker)1020 static uint16_t get_angle(lv_obj_t * cpicker)
1021 {
1022     lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);
1023     uint16_t angle;
1024     switch(ext->color_mode) {
1025         default:
1026         case LV_CPICKER_COLOR_MODE_HUE:
1027             angle = ext->hsv.h;
1028             break;
1029         case LV_CPICKER_COLOR_MODE_SATURATION:
1030             angle = (ext->hsv.s * 360) / 100;
1031             break;
1032         case LV_CPICKER_COLOR_MODE_VALUE:
1033             angle = (ext->hsv.v * 360) / 100 ;
1034             break;
1035     }
1036     return angle;
1037 }
1038 
1039 #endif /* LV_USE_CPICKER != 0 */
1040