1 /**
2  * @file lv_colorwheel.c
3  *
4  * Based on the work of @AloyseTech and @paulpv.
5  */
6 
7 /*********************
8  *      INCLUDES
9  *********************/
10 #include "lv_colorwheel.h"
11 #if LV_USE_COLORWHEEL
12 
13 #include "../../../misc/lv_assert.h"
14 
15 /*********************
16  *      DEFINES
17  *********************/
18 #define MY_CLASS &lv_colorwheel_class
19 
20 #define LV_CPICKER_DEF_QF 3
21 
22 /**
23  * 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
24  * multicoloured radial lines are calculated for the outer ring of the widget their lengths are jittering because of the
25  * integer based arithmetic. From tests the maximum delta was found to be 2 so the current value is set to 3 to achieve
26  * appropriate masking.
27  */
28 #define OUTER_MASK_WIDTH 3
29 
30 /**********************
31  *      TYPEDEFS
32  **********************/
33 
34 /**********************
35  *  STATIC PROTOTYPES
36  **********************/
37 static void lv_colorwheel_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
38 static void lv_colorwheel_event(const lv_obj_class_t * class_p, lv_event_t * e);
39 
40 static void draw_disc_grad(lv_event_t * e);
41 static void draw_knob(lv_event_t * e);
42 static void invalidate_knob(lv_obj_t * obj);
43 static lv_area_t get_knob_area(lv_obj_t * obj);
44 
45 static void next_color_mode(lv_obj_t * obj);
46 static lv_res_t double_click_reset(lv_obj_t * obj);
47 static void refr_knob_pos(lv_obj_t * obj);
48 static lv_color_t angle_to_mode_color_fast(lv_obj_t * obj, uint16_t angle);
49 static uint16_t get_angle(lv_obj_t * obj);
50 
51 /**********************
52  *  STATIC VARIABLES
53  **********************/
54 const lv_obj_class_t lv_colorwheel_class = {.instance_size = sizeof(lv_colorwheel_t), .base_class = &lv_obj_class,
55                                             .constructor_cb = lv_colorwheel_constructor,
56                                             .event_cb = lv_colorwheel_event,
57                                             .width_def = LV_DPI_DEF * 2,
58                                             .height_def = LV_DPI_DEF * 2,
59                                             .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
60                                            };
61 
62 static bool create_knob_recolor;
63 
64 /**********************
65  *      MACROS
66  **********************/
67 
68 /**********************
69  *   GLOBAL FUNCTIONS
70  **********************/
71 
72 /**
73  * Create a color_picker object
74  * @param parent pointer to an object, it will be the parent of the new color_picker
75  * @return pointer to the created color_picker
76  */
lv_colorwheel_create(lv_obj_t * parent,bool knob_recolor)77 lv_obj_t * lv_colorwheel_create(lv_obj_t * parent, bool knob_recolor)
78 {
79     LV_LOG_INFO("begin");
80     create_knob_recolor = knob_recolor;
81 
82     lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
83     lv_obj_class_init_obj(obj);
84     return obj;
85 }
86 
87 /*=====================
88  * Setter functions
89  *====================*/
90 
91 /**
92  * Set the current hsv of a color wheel.
93  * @param colorwheel pointer to color wheel object
94  * @param color current selected hsv
95  * @return true if changed, otherwise false
96  */
lv_colorwheel_set_hsv(lv_obj_t * obj,lv_color_hsv_t hsv)97 bool lv_colorwheel_set_hsv(lv_obj_t * obj, lv_color_hsv_t hsv)
98 {
99     if(hsv.h > 360) hsv.h %= 360;
100     if(hsv.s > 100) hsv.s = 100;
101     if(hsv.v > 100) hsv.v = 100;
102 
103     LV_ASSERT_OBJ(obj, MY_CLASS);
104     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
105 
106     if(colorwheel->hsv.h == hsv.h && colorwheel->hsv.s == hsv.s && colorwheel->hsv.v == hsv.v) return false;
107 
108     colorwheel->hsv = hsv;
109 
110     refr_knob_pos(obj);
111 
112     lv_obj_invalidate(obj);
113 
114     return true;
115 }
116 
117 /**
118  * Set the current color of a color wheel.
119  * @param colorwheel pointer to color wheel object
120  * @param color current selected color
121  * @return true if changed, otherwise false
122  */
lv_colorwheel_set_rgb(lv_obj_t * obj,lv_color_t color)123 bool lv_colorwheel_set_rgb(lv_obj_t * obj, lv_color_t color)
124 {
125     lv_color32_t c32;
126     c32.full = lv_color_to32(color);
127 
128     return lv_colorwheel_set_hsv(obj,  lv_color_rgb_to_hsv(c32.ch.red, c32.ch.green, c32.ch.blue));
129 }
130 
131 /**
132  * Set the current color mode.
133  * @param colorwheel pointer to color wheel object
134  * @param mode color mode (hue/sat/val)
135  */
lv_colorwheel_set_mode(lv_obj_t * obj,lv_colorwheel_mode_t mode)136 void lv_colorwheel_set_mode(lv_obj_t * obj, lv_colorwheel_mode_t mode)
137 {
138     LV_ASSERT_OBJ(obj, MY_CLASS);
139     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
140 
141     colorwheel->mode = mode;
142     refr_knob_pos(obj);
143     lv_obj_invalidate(obj);
144 }
145 
146 /**
147  * Set if the color mode is changed on long press on center
148  * @param colorwheel pointer to color wheel object
149  * @param fixed color mode cannot be changed on long press
150  */
lv_colorwheel_set_mode_fixed(lv_obj_t * obj,bool fixed)151 void lv_colorwheel_set_mode_fixed(lv_obj_t * obj, bool fixed)
152 {
153     LV_ASSERT_OBJ(obj, MY_CLASS);
154     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
155 
156     colorwheel->mode_fixed = fixed;
157 }
158 
159 /*=====================
160  * Getter functions
161  *====================*/
162 
163 /**
164  * Get the current selected hsv of a color wheel.
165  * @param colorwheel pointer to color wheel object
166  * @return current selected hsv
167  */
lv_colorwheel_get_hsv(lv_obj_t * obj)168 lv_color_hsv_t lv_colorwheel_get_hsv(lv_obj_t * obj)
169 {
170     LV_ASSERT_OBJ(obj, MY_CLASS);
171     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
172 
173     return colorwheel->hsv;
174 }
175 
176 /**
177  * Get the current selected color of a color wheel.
178  * @param colorwheel pointer to color wheel object
179  * @return color current selected color
180  */
lv_colorwheel_get_rgb(lv_obj_t * obj)181 lv_color_t lv_colorwheel_get_rgb(lv_obj_t * obj)
182 {
183     LV_ASSERT_OBJ(obj, MY_CLASS);
184     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
185 
186     return lv_color_hsv_to_rgb(colorwheel->hsv.h, colorwheel->hsv.s, colorwheel->hsv.v);
187 }
188 
189 /**
190  * Get the current color mode.
191  * @param colorwheel pointer to color wheel object
192  * @return color mode (hue/sat/val)
193  */
lv_colorwheel_get_color_mode(lv_obj_t * obj)194 lv_colorwheel_mode_t lv_colorwheel_get_color_mode(lv_obj_t * obj)
195 {
196     LV_ASSERT_OBJ(obj, MY_CLASS);
197     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
198 
199     return colorwheel->mode;
200 }
201 
202 /**
203  * Get if the color mode is changed on long press on center
204  * @param colorwheel pointer to color wheel object
205  * @return mode cannot be changed on long press
206  */
lv_colorwheel_get_color_mode_fixed(lv_obj_t * obj)207 bool lv_colorwheel_get_color_mode_fixed(lv_obj_t * obj)
208 {
209     LV_ASSERT_OBJ(obj, MY_CLASS);
210     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
211 
212     return colorwheel->mode_fixed;
213 }
214 
215 /*=====================
216  * Other functions
217  *====================*/
218 
219 /**********************
220  *   STATIC FUNCTIONS
221  **********************/
222 
lv_colorwheel_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)223 static void lv_colorwheel_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
224 {
225     LV_UNUSED(class_p);
226     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
227     colorwheel->hsv.h = 0;
228     colorwheel->hsv.s = 100;
229     colorwheel->hsv.v = 100;
230     colorwheel->mode = LV_COLORWHEEL_MODE_HUE;
231     colorwheel->mode_fixed = 0;
232     colorwheel->last_click_time = 0;
233     colorwheel->last_change_time = 0;
234     colorwheel->knob.recolor = create_knob_recolor;
235 
236     lv_obj_add_flag(obj, LV_OBJ_FLAG_ADV_HITTEST);
237     lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN);
238     refr_knob_pos(obj);
239 }
240 
draw_disc_grad(lv_event_t * e)241 static void draw_disc_grad(lv_event_t * e)
242 {
243     lv_obj_t * obj = lv_event_get_target(e);
244     lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
245     lv_coord_t w = lv_obj_get_width(obj);
246     lv_coord_t h = lv_obj_get_height(obj);
247     lv_coord_t cx = obj->coords.x1 + w / 2;
248     lv_coord_t cy = obj->coords.y1 + h / 2;
249     lv_coord_t r = w / 2;
250 
251     lv_draw_line_dsc_t line_dsc;
252     lv_draw_line_dsc_init(&line_dsc);
253     lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &line_dsc);
254 
255     line_dsc.width = (r * 628 / (256 / LV_CPICKER_DEF_QF)) / 100;
256     line_dsc.width += 2;
257     uint16_t i;
258     uint32_t a = 0;
259     lv_coord_t cir_w = lv_obj_get_style_arc_width(obj, LV_PART_MAIN);
260 
261 #if LV_DRAW_COMPLEX
262     /*Mask outer and inner ring of widget to tidy up ragged edges of lines while drawing outer ring*/
263     lv_draw_mask_radius_param_t mask_out_param;
264     lv_draw_mask_radius_init(&mask_out_param, &obj->coords, LV_RADIUS_CIRCLE, false);
265     int16_t mask_out_id = lv_draw_mask_add(&mask_out_param, 0);
266 
267     lv_area_t mask_area;
268     lv_area_copy(&mask_area, &obj->coords);
269     mask_area.x1 += cir_w;
270     mask_area.x2 -= cir_w;
271     mask_area.y1 += cir_w;
272     mask_area.y2 -= cir_w;
273     lv_draw_mask_radius_param_t mask_in_param;
274     lv_draw_mask_radius_init(&mask_in_param, &mask_area, LV_RADIUS_CIRCLE, true);
275     int16_t mask_in_id = lv_draw_mask_add(&mask_in_param, 0);
276 
277     /*The inner and outer line ends will be masked out.
278      *So make lines a little bit longer because the masking makes a more even result*/
279     lv_coord_t cir_w_extra = line_dsc.width;
280 #else
281     lv_coord_t cir_w_extra = 0;
282 #endif
283 
284     for(i = 0; i <= 256; i += LV_CPICKER_DEF_QF, a += 360 * LV_CPICKER_DEF_QF) {
285         line_dsc.color = angle_to_mode_color_fast(obj, i);
286         uint16_t angle_trigo = (uint16_t)(a >> 8); /*i * 360 / 256 is the scale to apply, but we can skip multiplication here*/
287 
288         lv_point_t p[2];
289         p[0].x = cx + ((r + cir_w_extra) * lv_trigo_sin(angle_trigo) >> LV_TRIGO_SHIFT);
290         p[0].y = cy + ((r + cir_w_extra) * lv_trigo_cos(angle_trigo) >> LV_TRIGO_SHIFT);
291         p[1].x = cx + ((r - cir_w - cir_w_extra) * lv_trigo_sin(angle_trigo) >> LV_TRIGO_SHIFT);
292         p[1].y = cy + ((r - cir_w - cir_w_extra) * lv_trigo_cos(angle_trigo) >> LV_TRIGO_SHIFT);
293 
294         lv_draw_line(draw_ctx, &line_dsc, &p[0], &p[1]);
295     }
296 
297 #if LV_DRAW_COMPLEX
298     lv_draw_mask_free_param(&mask_out_param);
299     lv_draw_mask_free_param(&mask_in_param);
300     lv_draw_mask_remove_id(mask_out_id);
301     lv_draw_mask_remove_id(mask_in_id);
302 #endif
303 }
304 
draw_knob(lv_event_t * e)305 static void draw_knob(lv_event_t * e)
306 {
307     lv_obj_t * obj = lv_event_get_target(e);
308     lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
309     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
310 
311     lv_draw_rect_dsc_t cir_dsc;
312     lv_draw_rect_dsc_init(&cir_dsc);
313     lv_obj_init_draw_rect_dsc(obj, LV_PART_KNOB, &cir_dsc);
314 
315     cir_dsc.radius = LV_RADIUS_CIRCLE;
316 
317     if(colorwheel->knob.recolor) {
318         cir_dsc.bg_color = lv_colorwheel_get_rgb(obj);
319     }
320 
321     lv_area_t knob_area = get_knob_area(obj);
322 
323     lv_draw_rect(draw_ctx, &cir_dsc, &knob_area);
324 }
325 
invalidate_knob(lv_obj_t * obj)326 static void invalidate_knob(lv_obj_t * obj)
327 {
328     lv_area_t knob_area = get_knob_area(obj);
329 
330     lv_obj_invalidate_area(obj, &knob_area);
331 }
332 
get_knob_area(lv_obj_t * obj)333 static lv_area_t get_knob_area(lv_obj_t * obj)
334 {
335     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
336 
337     /*Get knob's radius*/
338     uint16_t r = 0;
339     r = lv_obj_get_style_arc_width(obj, LV_PART_MAIN) / 2;
340 
341     lv_coord_t left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB);
342     lv_coord_t right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB);
343     lv_coord_t top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB);
344     lv_coord_t bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB);
345 
346     lv_area_t knob_area;
347     knob_area.x1 = obj->coords.x1 + colorwheel->knob.pos.x - r - left;
348     knob_area.y1 = obj->coords.y1 + colorwheel->knob.pos.y - r - right;
349     knob_area.x2 = obj->coords.x1 + colorwheel->knob.pos.x + r + top;
350     knob_area.y2 = obj->coords.y1 + colorwheel->knob.pos.y + r + bottom;
351 
352     return knob_area;
353 }
354 
lv_colorwheel_event(const lv_obj_class_t * class_p,lv_event_t * e)355 static void lv_colorwheel_event(const lv_obj_class_t * class_p, lv_event_t * e)
356 {
357     LV_UNUSED(class_p);
358 
359     /*Call the ancestor's event handler*/
360     lv_res_t res = lv_obj_event_base(MY_CLASS, e);
361 
362     if(res != LV_RES_OK) return;
363 
364     lv_event_code_t code = lv_event_get_code(e);
365     lv_obj_t * obj = lv_event_get_target(e);
366     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
367 
368     if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
369         lv_coord_t left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB);
370         lv_coord_t right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB);
371         lv_coord_t top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB);
372         lv_coord_t bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB);
373 
374         lv_coord_t knob_pad = LV_MAX4(left, right, top, bottom) + 2;
375         lv_coord_t * s = lv_event_get_param(e);
376         *s = LV_MAX(*s, knob_pad);
377     }
378     else if(code == LV_EVENT_SIZE_CHANGED) {
379         void * param = lv_event_get_param(e);
380         /*Refresh extended draw area to make knob visible*/
381         if(lv_obj_get_width(obj) != lv_area_get_width(param) ||
382            lv_obj_get_height(obj) != lv_area_get_height(param)) {
383             refr_knob_pos(obj);
384         }
385     }
386     else if(code == LV_EVENT_STYLE_CHANGED) {
387         /*Refresh extended draw area to make knob visible*/
388         refr_knob_pos(obj);
389     }
390     else if(code == LV_EVENT_KEY) {
391         uint32_t c = *((uint32_t *)lv_event_get_param(e)); /*uint32_t because can be UTF-8*/
392 
393         if(c == LV_KEY_RIGHT || c == LV_KEY_UP) {
394             lv_color_hsv_t hsv_cur;
395             hsv_cur = colorwheel->hsv;
396 
397             switch(colorwheel->mode) {
398                 case LV_COLORWHEEL_MODE_HUE:
399                     hsv_cur.h = (colorwheel->hsv.h + 1) % 360;
400                     break;
401                 case LV_COLORWHEEL_MODE_SATURATION:
402                     hsv_cur.s = (colorwheel->hsv.s + 1) % 100;
403                     break;
404                 case LV_COLORWHEEL_MODE_VALUE:
405                     hsv_cur.v = (colorwheel->hsv.v + 1) % 100;
406                     break;
407             }
408 
409             if(lv_colorwheel_set_hsv(obj, hsv_cur)) {
410                 res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
411                 if(res != LV_RES_OK) return;
412             }
413         }
414         else if(c == LV_KEY_LEFT || c == LV_KEY_DOWN) {
415             lv_color_hsv_t hsv_cur;
416             hsv_cur = colorwheel->hsv;
417 
418             switch(colorwheel->mode) {
419                 case LV_COLORWHEEL_MODE_HUE:
420                     hsv_cur.h = colorwheel->hsv.h > 0 ? (colorwheel->hsv.h - 1) : 360;
421                     break;
422                 case LV_COLORWHEEL_MODE_SATURATION:
423                     hsv_cur.s = colorwheel->hsv.s > 0 ? (colorwheel->hsv.s - 1) : 100;
424                     break;
425                 case LV_COLORWHEEL_MODE_VALUE:
426                     hsv_cur.v = colorwheel->hsv.v > 0 ? (colorwheel->hsv.v - 1) : 100;
427                     break;
428             }
429 
430             if(lv_colorwheel_set_hsv(obj, hsv_cur)) {
431                 res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
432                 if(res != LV_RES_OK) return;
433             }
434         }
435     }
436     else if(code == LV_EVENT_PRESSED) {
437         colorwheel->last_change_time = lv_tick_get();
438         lv_indev_get_point(lv_indev_get_act(), &colorwheel->last_press_point);
439         res = double_click_reset(obj);
440         if(res != LV_RES_OK) return;
441     }
442     else if(code == LV_EVENT_PRESSING) {
443         lv_indev_t * indev = lv_indev_get_act();
444         if(indev == NULL) return;
445 
446         lv_indev_type_t indev_type = lv_indev_get_type(indev);
447         lv_point_t p;
448         if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) {
449             p.x = obj->coords.x1 + lv_obj_get_width(obj) / 2;
450             p.y = obj->coords.y1 + lv_obj_get_height(obj) / 2;
451         }
452         else {
453             lv_indev_get_point(indev, &p);
454         }
455 
456         lv_coord_t drag_limit = indev->driver->scroll_limit;
457         if((LV_ABS(p.x - colorwheel->last_press_point.x) > drag_limit) ||
458            (LV_ABS(p.y - colorwheel->last_press_point.y) > drag_limit)) {
459             colorwheel->last_change_time = lv_tick_get();
460             colorwheel->last_press_point.x = p.x;
461             colorwheel->last_press_point.y = p.y;
462         }
463 
464         p.x -= obj->coords.x1;
465         p.y -= obj->coords.y1;
466 
467         /*Ignore pressing in the inner area*/
468         uint16_t w = lv_obj_get_width(obj);
469 
470         int16_t angle = 0;
471         lv_coord_t cir_w = lv_obj_get_style_arc_width(obj, LV_PART_MAIN);
472 
473         lv_coord_t r_in = w / 2;
474         p.x -= r_in;
475         p.y -= r_in;
476         bool on_ring = true;
477         r_in -= cir_w;
478         if(r_in > LV_DPI_DEF / 2) {
479             lv_coord_t inner = cir_w / 2;
480             r_in -= inner;
481 
482             if(r_in < LV_DPI_DEF / 2) r_in = LV_DPI_DEF / 2;
483         }
484 
485         if(p.x * p.x + p.y * p.y < r_in * r_in) {
486             on_ring = false;
487         }
488 
489         /*If the inner area is being pressed, go to the next color mode on long press*/
490         uint32_t diff = lv_tick_elaps(colorwheel->last_change_time);
491         if(!on_ring && diff > indev->driver->long_press_time && !colorwheel->mode_fixed) {
492             next_color_mode(obj);
493             lv_indev_wait_release(lv_indev_get_act());
494             return;
495         }
496 
497         /*Set the angle only if pressed on the ring*/
498         if(!on_ring) return;
499 
500         angle = lv_atan2(p.x, p.y) % 360;
501 
502         lv_color_hsv_t hsv_cur;
503         hsv_cur = colorwheel->hsv;
504 
505         switch(colorwheel->mode) {
506             case LV_COLORWHEEL_MODE_HUE:
507                 hsv_cur.h = angle;
508                 break;
509             case LV_COLORWHEEL_MODE_SATURATION:
510                 hsv_cur.s = (angle * 100) / 360;
511                 break;
512             case LV_COLORWHEEL_MODE_VALUE:
513                 hsv_cur.v = (angle * 100) / 360;
514                 break;
515         }
516 
517         if(lv_colorwheel_set_hsv(obj, hsv_cur)) {
518             res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
519             if(res != LV_RES_OK) return;
520         }
521     }
522     else if(code == LV_EVENT_HIT_TEST) {
523         lv_hit_test_info_t * info = lv_event_get_param(e);;
524 
525         /*Valid clicks can be only in the circle*/
526         info->res = _lv_area_is_point_on(&obj->coords, info->point, LV_RADIUS_CIRCLE);
527     }
528     else if(code == LV_EVENT_DRAW_MAIN) {
529         draw_disc_grad(e);
530         draw_knob(e);
531     }
532     else if(code == LV_EVENT_COVER_CHECK) {
533         lv_cover_check_info_t * info = lv_event_get_param(e);
534         if(info->res != LV_COVER_RES_MASKED) info->res = LV_COVER_RES_NOT_COVER;
535     }
536 }
537 
next_color_mode(lv_obj_t * obj)538 static void next_color_mode(lv_obj_t * obj)
539 {
540     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
541     colorwheel->mode = (colorwheel->mode + 1) % 3;
542     refr_knob_pos(obj);
543     lv_obj_invalidate(obj);
544 }
545 
refr_knob_pos(lv_obj_t * obj)546 static void refr_knob_pos(lv_obj_t * obj)
547 {
548     invalidate_knob(obj);
549 
550     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
551     lv_coord_t w = lv_obj_get_width(obj);
552 
553     lv_coord_t scale_w = lv_obj_get_style_arc_width(obj, LV_PART_MAIN);
554     lv_coord_t r = (w - scale_w) / 2;
555     uint16_t angle = get_angle(obj);
556     colorwheel->knob.pos.x = (((int32_t)r * lv_trigo_sin(angle)) >> LV_TRIGO_SHIFT);
557     colorwheel->knob.pos.y = (((int32_t)r * lv_trigo_cos(angle)) >> LV_TRIGO_SHIFT);
558     colorwheel->knob.pos.x = colorwheel->knob.pos.x + w / 2;
559     colorwheel->knob.pos.y = colorwheel->knob.pos.y + w / 2;
560 
561     invalidate_knob(obj);
562 }
563 
double_click_reset(lv_obj_t * obj)564 static lv_res_t double_click_reset(lv_obj_t * obj)
565 {
566     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
567     lv_indev_t * indev = lv_indev_get_act();
568     /*Double clicked? Use long press time as double click time out*/
569     if(lv_tick_elaps(colorwheel->last_click_time) < indev->driver->long_press_time) {
570         lv_color_hsv_t hsv_cur;
571         hsv_cur = colorwheel->hsv;
572 
573         switch(colorwheel->mode) {
574             case LV_COLORWHEEL_MODE_HUE:
575                 hsv_cur.h = 0;
576                 break;
577             case LV_COLORWHEEL_MODE_SATURATION:
578                 hsv_cur.s = 100;
579                 break;
580             case LV_COLORWHEEL_MODE_VALUE:
581                 hsv_cur.v = 100;
582                 break;
583         }
584 
585         lv_indev_wait_release(indev);
586 
587         if(lv_colorwheel_set_hsv(obj, hsv_cur)) {
588             lv_res_t res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
589             if(res != LV_RES_OK) return res;
590         }
591     }
592     colorwheel->last_click_time = lv_tick_get();
593 
594     return LV_RES_OK;
595 }
596 
597 #define SWAPPTR(A, B) do { uint8_t * t = A; A = B; B = t; } while(0)
598 #define HSV_PTR_SWAP(sextant,r,g,b)     if((sextant) & 2) { SWAPPTR((r), (b)); } if((sextant) & 4) { SWAPPTR((g), (b)); } if(!((sextant) & 6)) { \
599         if(!((sextant) & 1)) { SWAPPTR((r), (g)); } } else { if((sextant) & 1) { SWAPPTR((r), (g)); } }
600 
601 /**
602  * Based on the idea from https://www.vagrearg.org/content/hsvrgb
603  * Here we want to compute an approximate RGB value from a HSV input color space. We don't want to be accurate
604  * (for that, there's lv_color_hsv_to_rgb), but we want to be fast.
605  *
606  * Few tricks are used here: Hue is in range [0; 6 * 256] (so that the sextant is in the high byte and the fractional part is in the low byte)
607  * both s and v are in [0; 255] range (very convenient to avoid divisions).
608  *
609  * We fold all symmetry by swapping the R, G, B pointers so that the code is the same for all sextants.
610  * We replace division by 255 by a division by 256, a.k.a a shift right by 8 bits.
611  * This is wrong, but since this is only used to compute the pixels on the screen and not the final color, it's ok.
612  */
fast_hsv2rgb(uint16_t h,uint8_t s,uint8_t v,uint8_t * r,uint8_t * g,uint8_t * b)613 static void fast_hsv2rgb(uint16_t h, uint8_t s, uint8_t v, uint8_t * r, uint8_t * g, uint8_t * b)
614 {
615     if(!s) {
616         *r = *g = *b = v;
617         return;
618     }
619 
620     uint8_t sextant = h >> 8;
621     HSV_PTR_SWAP(sextant, r, g, b); /*Swap pointers so the conversion code is the same*/
622 
623     *g = v;
624 
625     uint8_t bb = ~s;
626     uint16_t ww = v * bb; /*Don't try to be precise, but instead, be fast*/
627     *b = ww >> 8;
628 
629     uint8_t h_frac = h & 0xff;
630 
631     if(!(sextant & 1)) {
632         /*Up slope*/
633         ww = !h_frac ? ((uint16_t)s << 8) : (s * (uint8_t)(-h_frac)); /*Skip multiply if not required*/
634     }
635     else {
636         /*Down slope*/
637         ww = s * h_frac;
638     }
639     bb = ww >> 8;
640     bb = ~bb;
641     ww = v * bb;
642     *r = ww >> 8;
643 }
644 
angle_to_mode_color_fast(lv_obj_t * obj,uint16_t angle)645 static lv_color_t angle_to_mode_color_fast(lv_obj_t * obj, uint16_t angle)
646 {
647     lv_colorwheel_t * ext = (lv_colorwheel_t *)obj;
648     uint8_t r = 0, g = 0, b = 0;
649     static uint16_t h = 0;
650     static uint8_t s = 0, v = 0, m = 255;
651     static uint16_t angle_saved = 0xffff;
652 
653     /*If the angle is different recalculate scaling*/
654     if(angle_saved != angle) m = 255;
655     angle_saved = angle;
656 
657     switch(ext->mode) {
658         default:
659         case LV_COLORWHEEL_MODE_HUE:
660             /*Don't recompute costly scaling if it does not change*/
661             if(m != ext->mode) {
662                 s = (uint8_t)(((uint16_t)ext->hsv.s * 51) / 20);
663                 v = (uint8_t)(((uint16_t)ext->hsv.v * 51) / 20);
664                 m = ext->mode;
665             }
666             fast_hsv2rgb(angle * 6, s, v, &r, &g,
667                          &b); /*A smart compiler will replace x * 6 by (x << 2) + (x << 1) if it's more efficient*/
668             break;
669         case LV_COLORWHEEL_MODE_SATURATION:
670             /*Don't recompute costly scaling if it does not change*/
671             if(m != ext->mode) {
672                 h = (uint16_t)(((uint32_t)ext->hsv.h * 6 * 256) / 360);
673                 v = (uint8_t)(((uint16_t)ext->hsv.v * 51) / 20);
674                 m = ext->mode;
675             }
676             fast_hsv2rgb(h, angle, v, &r, &g, &b);
677             break;
678         case LV_COLORWHEEL_MODE_VALUE:
679             /*Don't recompute costly scaling if it does not change*/
680             if(m != ext->mode) {
681                 h = (uint16_t)(((uint32_t)ext->hsv.h * 6 * 256) / 360);
682                 s = (uint8_t)(((uint16_t)ext->hsv.s * 51) / 20);
683                 m = ext->mode;
684             }
685             fast_hsv2rgb(h, s, angle, &r, &g, &b);
686             break;
687     }
688     return lv_color_make(r, g, b);
689 }
690 
get_angle(lv_obj_t * obj)691 static uint16_t get_angle(lv_obj_t * obj)
692 {
693     lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj;
694     uint16_t angle;
695     switch(colorwheel->mode) {
696         default:
697         case LV_COLORWHEEL_MODE_HUE:
698             angle = colorwheel->hsv.h;
699             break;
700         case LV_COLORWHEEL_MODE_SATURATION:
701             angle = (colorwheel->hsv.s * 360) / 100;
702             break;
703         case LV_COLORWHEEL_MODE_VALUE:
704             angle = (colorwheel->hsv.v * 360) / 100 ;
705             break;
706     }
707     return angle;
708 }
709 
710 #endif /*LV_USE_COLORWHEEL*/
711