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