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