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