1 /**
2  * @file lv_spinbox.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_spinbox.h"
10 
11 #if LV_USE_SPINBOX != 0
12 #include "../lv_misc/lv_debug.h"
13 #include "../lv_themes/lv_theme.h"
14 #include "../lv_misc/lv_math.h"
15 #include "../lv_misc/lv_utils.h"
16 
17 /*********************
18  *      DEFINES
19  *********************/
20 #define LV_OBJX_NAME "lv_spinbox"
21 
22 /**********************
23  *      TYPEDEFS
24  **********************/
25 
26 /**********************
27  *  STATIC PROTOTYPES
28  **********************/
29 static lv_res_t lv_spinbox_signal(lv_obj_t * spinbox, lv_signal_t sign, void * param);
30 static lv_style_list_t * lv_spinbox_get_style(lv_obj_t * ta, uint8_t part);
31 static void lv_spinbox_updatevalue(lv_obj_t * spinbox);
32 
33 /**********************
34  *  STATIC VARIABLES
35  **********************/
36 static lv_signal_cb_t ancestor_signal;
37 static lv_design_cb_t ancestor_design;
38 
39 /**********************
40  *      MACROS
41  **********************/
42 
43 /**********************
44  *   GLOBAL FUNCTIONS
45  **********************/
46 
47 /**
48  * Create a spinbox object
49  * @param par pointer to an object, it will be the parent of the new spinbox
50  * @param copy pointer to a spinbox object, if not NULL then the new object will be copied from it
51  * @return pointer to the created spinbox
52  */
lv_spinbox_create(lv_obj_t * par,const lv_obj_t * copy)53 lv_obj_t * lv_spinbox_create(lv_obj_t * par, const lv_obj_t * copy)
54 {
55     LV_LOG_TRACE("spinbox create started");
56 
57     /*Create the ancestor of spinbox*/
58     lv_obj_t * spinbox = lv_textarea_create(par, copy);
59     LV_ASSERT_MEM(spinbox);
60     if(spinbox == NULL) return NULL;
61 
62     /*Allocate the spinbox type specific extended data*/
63     lv_spinbox_ext_t * ext = lv_obj_allocate_ext_attr(spinbox, sizeof(lv_spinbox_ext_t));
64     LV_ASSERT_MEM(ext);
65     if(ext == NULL) {
66         lv_obj_del(spinbox);
67         return NULL;
68     }
69 
70     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(spinbox);
71     if(ancestor_design == NULL) ancestor_design = lv_obj_get_design_cb(spinbox);
72 
73     /*Initialize the allocated 'ext'*/
74     ext->value              = 0;
75     ext->dec_point_pos      = 0;
76     ext->digit_count        = 5;
77     ext->digit_padding_left = 0;
78     ext->step               = 1;
79     ext->range_max          = 99999;
80     ext->range_min          = -99999;
81     ext->rollover           = false;
82 
83 
84     /*The signal and design functions are not copied so set them here*/
85     lv_obj_set_signal_cb(spinbox, lv_spinbox_signal);
86     lv_obj_set_design_cb(spinbox, ancestor_design); /*Leave the Text area's design function*/
87 
88     /*Init the new spinbox*/
89     if(copy == NULL) {
90         /* No scrolling will happen here so make the scrollable non-clickable
91          * It allows to handle input events in the bg object only.*/
92         lv_obj_set_click(lv_page_get_scrollable(spinbox), false);
93         lv_textarea_set_one_line(spinbox, true);
94         lv_textarea_set_cursor_click_pos(spinbox, true);
95         lv_obj_set_width(spinbox, LV_DPI);
96         lv_theme_apply(spinbox, LV_THEME_SPINBOX);
97     }
98     /*Copy an existing spinbox*/
99     else {
100         lv_spinbox_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
101 
102         lv_spinbox_set_value(spinbox, copy_ext->value);
103         lv_spinbox_set_digit_format(spinbox, (uint8_t)copy_ext->digit_count, (uint8_t)copy_ext->dec_point_pos);
104         lv_spinbox_set_range(spinbox, copy_ext->range_min, copy_ext->range_max);
105         lv_spinbox_set_step(spinbox, copy_ext->step);
106         lv_spinbox_set_rollover(spinbox, copy_ext->rollover);
107 
108         /*Refresh the style with new signal function*/
109         lv_obj_refresh_style(spinbox, LV_OBJ_PART_ALL, LV_STYLE_PROP_ALL);
110     }
111 
112     lv_spinbox_updatevalue(spinbox);
113 
114     LV_LOG_INFO("spinbox created");
115 
116     return spinbox;
117 }
118 
119 /*=====================
120  * Setter functions
121  *====================*/
122 
123 /**
124  * Set spinbox rollover function
125  * @param spinbox pointer to spinbox
126  * @param b true or false to enable or disable (default)
127  */
lv_spinbox_set_rollover(lv_obj_t * spinbox,bool b)128 void lv_spinbox_set_rollover(lv_obj_t * spinbox, bool b)
129 {
130     LV_ASSERT_OBJ(spinbox, LV_OBJX_NAME);
131 
132     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
133 
134     ext->rollover = b;
135 }
136 
137 /**
138  * Set spinbox value
139  * @param spinbox pointer to spinbox
140  * @param i value to be set
141  */
lv_spinbox_set_value(lv_obj_t * spinbox,int32_t i)142 void lv_spinbox_set_value(lv_obj_t * spinbox, int32_t i)
143 {
144     LV_ASSERT_OBJ(spinbox, LV_OBJX_NAME);
145 
146     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
147     if(ext == NULL) return;
148 
149     if(i > ext->range_max) i = ext->range_max;
150     if(i < ext->range_min) i = ext->range_min;
151 
152     ext->value = i;
153 
154     lv_spinbox_updatevalue(spinbox);
155 }
156 
157 /**
158  * Set spinbox digit format (digit count and decimal format)
159  * @param spinbox pointer to spinbox
160  * @param digit_count number of digit excluding the decimal separator and the sign
161  * @param separator_position number of digit before the decimal point. If 0, decimal point is not
162  * shown
163  */
lv_spinbox_set_digit_format(lv_obj_t * spinbox,uint8_t digit_count,uint8_t separator_position)164 void lv_spinbox_set_digit_format(lv_obj_t * spinbox, uint8_t digit_count, uint8_t separator_position)
165 {
166     LV_ASSERT_OBJ(spinbox, LV_OBJX_NAME);
167 
168     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
169     if(ext == NULL) return;
170 
171     if(digit_count > LV_SPINBOX_MAX_DIGIT_COUNT) digit_count = LV_SPINBOX_MAX_DIGIT_COUNT;
172 
173     if(separator_position >= digit_count) separator_position = 0;
174     if(separator_position > LV_SPINBOX_MAX_DIGIT_COUNT) separator_position = LV_SPINBOX_MAX_DIGIT_COUNT;
175 
176     if(digit_count < LV_SPINBOX_MAX_DIGIT_COUNT) {
177         int64_t max_val = _lv_pow(10, digit_count);
178         if(ext->range_max > max_val - 1) ext->range_max = max_val - 1;
179         if(ext->range_min < - max_val  + 1) ext->range_min = - max_val  + 1;
180     }
181 
182     ext->digit_count   = digit_count;
183     ext->dec_point_pos = separator_position;
184 
185     lv_spinbox_updatevalue(spinbox);
186 }
187 
188 /**
189  * Set spinbox step
190  * @param spinbox pointer to spinbox
191  * @param step steps on increment/decrement
192  */
lv_spinbox_set_step(lv_obj_t * spinbox,uint32_t step)193 void lv_spinbox_set_step(lv_obj_t * spinbox, uint32_t step)
194 {
195     LV_ASSERT_OBJ(spinbox, LV_OBJX_NAME);
196 
197     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
198     if(ext == NULL) return;
199 
200     ext->step = step;
201 }
202 
203 /**
204  * Set spinbox value range
205  * @param spinbox pointer to spinbox
206  * @param range_min maximum value, inclusive
207  * @param range_max minimum value, inclusive
208  */
lv_spinbox_set_range(lv_obj_t * spinbox,int32_t range_min,int32_t range_max)209 void lv_spinbox_set_range(lv_obj_t * spinbox, int32_t range_min, int32_t range_max)
210 {
211     LV_ASSERT_OBJ(spinbox, LV_OBJX_NAME);
212 
213     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
214     if(ext == NULL) return;
215 
216     ext->range_max = range_max;
217     ext->range_min = range_min;
218 
219     if(ext->value > ext->range_max) {
220         ext->value = ext->range_max;
221         lv_obj_invalidate(spinbox);
222     }
223     if(ext->value < ext->range_min) {
224         ext->value = ext->range_min;
225         lv_obj_invalidate(spinbox);
226     }
227 
228     lv_spinbox_updatevalue(spinbox);
229 }
230 
231 /**
232  * Set spinbox left padding in digits count (added between sign and first digit)
233  * @param spinbox pointer to spinbox
234  * @param cb Callback function called on value change event
235  */
lv_spinbox_set_padding_left(lv_obj_t * spinbox,uint8_t padding)236 void lv_spinbox_set_padding_left(lv_obj_t * spinbox, uint8_t padding)
237 {
238     LV_ASSERT_OBJ(spinbox, LV_OBJX_NAME);
239 
240     lv_spinbox_ext_t * ext  = lv_obj_get_ext_attr(spinbox);
241     ext->digit_padding_left = padding;
242     lv_spinbox_updatevalue(spinbox);
243 }
244 
245 /*=====================
246  * Getter functions
247  *====================*/
248 
249 /**
250  * Get spinbox rollover function status
251  * @param spinbox pointer to spinbox
252  */
lv_spinbox_get_rollover(lv_obj_t * spinbox)253 bool lv_spinbox_get_rollover(lv_obj_t * spinbox)
254 {
255     LV_ASSERT_OBJ(spinbox, LV_OBJX_NAME);
256 
257     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
258 
259     return ext->rollover;
260 }
261 
262 /**
263  * Get the spinbox numeral value (user has to convert to float according to its digit format)
264  * @param spinbox pointer to spinbox
265  * @return value integer value of the spinbox
266  */
lv_spinbox_get_value(lv_obj_t * spinbox)267 int32_t lv_spinbox_get_value(lv_obj_t * spinbox)
268 {
269     LV_ASSERT_OBJ(spinbox, LV_OBJX_NAME);
270 
271     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
272 
273     return ext->value;
274 }
275 
276 /*=====================
277  * Other functions
278  *====================*/
279 
280 /**
281  * Select next lower digit for edition
282  * @param spinbox pointer to spinbox
283  */
lv_spinbox_step_next(lv_obj_t * spinbox)284 void lv_spinbox_step_next(lv_obj_t * spinbox)
285 {
286     LV_ASSERT_OBJ(spinbox, LV_OBJX_NAME);
287 
288     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
289 
290     int32_t new_step = ext->step / 10;
291     if((new_step) > 0)
292         ext->step = new_step;
293     else
294         ext->step = 1;
295 
296     lv_spinbox_updatevalue(spinbox);
297 }
298 
299 /**
300  * Select next higher digit for edition
301  * @param spinbox pointer to spinbox
302  */
lv_spinbox_step_prev(lv_obj_t * spinbox)303 void lv_spinbox_step_prev(lv_obj_t * spinbox)
304 {
305     LV_ASSERT_OBJ(spinbox, LV_OBJX_NAME);
306 
307     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
308     int32_t step_limit;
309     step_limit       = LV_MATH_MAX(ext->range_max, (ext->range_min < 0 ? (-ext->range_min) : ext->range_min));
310     int32_t new_step = ext->step * 10;
311     if(new_step <= step_limit) ext->step = new_step;
312 
313     lv_spinbox_updatevalue(spinbox);
314 }
315 
316 /**
317  * Increment spinbox value by one step
318  * @param spinbox pointer to spinbox
319  */
lv_spinbox_increment(lv_obj_t * spinbox)320 void lv_spinbox_increment(lv_obj_t * spinbox)
321 {
322     LV_ASSERT_OBJ(spinbox, LV_OBJX_NAME);
323 
324     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
325 
326     if(ext->value + ext->step <= ext->range_max) {
327         /*Special mode when zero crossing*/
328         if((ext->value + ext->step) > 0 && ext->value < 0) ext->value = -ext->value;
329         ext->value += ext->step;
330 
331     }
332     else {
333         // Rollover?
334         if((ext->rollover) && (ext->value == ext->range_max))
335             ext->value = ext->range_min;
336         else
337             ext->value = ext->range_max;
338     }
339 
340     lv_spinbox_updatevalue(spinbox);
341 }
342 
343 /**
344  * Decrement spinbox value by one step
345  * @param spinbox pointer to spinbox
346  */
lv_spinbox_decrement(lv_obj_t * spinbox)347 void lv_spinbox_decrement(lv_obj_t * spinbox)
348 {
349     LV_ASSERT_OBJ(spinbox, LV_OBJX_NAME);
350 
351     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
352 
353     if(ext->value - ext->step >= ext->range_min) {
354         /*Special mode when zero crossing*/
355         if((ext->value - ext->step) < 0 && ext->value > 0) ext->value = -ext->value;
356         ext->value -= ext->step;
357     }
358     else {
359         // Rollover?
360         if((ext->rollover) && (ext->value == ext->range_min))
361             ext->value = ext->range_max;
362         else
363             ext->value = ext->range_min;
364     }
365 
366     lv_spinbox_updatevalue(spinbox);
367 }
368 
369 /**********************
370  *   STATIC FUNCTIONS
371  **********************/
372 
373 /**
374  * Signal function of the spinbox
375  * @param spinbox pointer to a spinbox object
376  * @param sign a signal type from lv_signal_t enum
377  * @param param pointer to a signal specific variable
378  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
379  */
lv_spinbox_signal(lv_obj_t * spinbox,lv_signal_t sign,void * param)380 static lv_res_t lv_spinbox_signal(lv_obj_t * spinbox, lv_signal_t sign, void * param)
381 {
382 
383     lv_res_t res = LV_RES_OK;
384     if(sign == LV_SIGNAL_GET_STYLE) {
385         lv_get_style_info_t * info = param;
386         info->result = lv_spinbox_get_style(spinbox, info->part);
387         if(info->result != NULL) return LV_RES_OK;
388         else return ancestor_signal(spinbox, sign, param);
389     }
390 
391     /* Include the ancient signal function */
392     if(sign != LV_SIGNAL_CONTROL) {
393         res = ancestor_signal(spinbox, sign, param);
394         if(res != LV_RES_OK) return res;
395     }
396     if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
397 
398 
399     if(sign == LV_SIGNAL_CLEANUP) {
400         /*Nothing to cleanup. (No dynamically allocated memory in 'ext')*/
401     }
402     else if(sign == LV_SIGNAL_GET_TYPE) {
403         lv_obj_type_t * buf = param;
404         uint8_t i;
405         for(i = 0; i < LV_MAX_ANCESTOR_NUM - 1; i++) { /*Find the last set data*/
406             if(buf->type[i] == NULL) break;
407         }
408         buf->type[i] = "lv_spinbox";
409     }
410     else if(sign == LV_SIGNAL_RELEASED) {
411         /*If released with an ENCODER then move to the next digit*/
412         lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
413         lv_indev_t * indev = lv_indev_get_act();
414         if(lv_indev_get_type(indev) == LV_INDEV_TYPE_ENCODER) {
415 #if LV_USE_GROUP
416             if(lv_group_get_editing(lv_obj_get_group(spinbox))) {
417                 if(ext->step > 1) {
418                     lv_spinbox_step_next(spinbox);
419                 }
420                 else {
421                     /*Restart from the MSB*/
422                     ext->step = 1;
423                     uint32_t i;
424                     for(i = 0; i < ext->digit_count; i++) {
425                         int32_t new_step = ext->step * 10;
426                         if(new_step >= ext->range_max) break;
427                         ext->step = new_step;
428                     }
429                     lv_spinbox_step_prev(spinbox);
430                 }
431             }
432 #endif
433         }
434         else {
435             /*The cursor has been positioned to a digit.
436              * Set `step` accordingly*/
437             const char * txt = lv_textarea_get_text(spinbox);
438             size_t txt_len = strlen(txt);
439 
440             if(txt[ext->ta.cursor.pos] == '.') {
441                 lv_textarea_cursor_left(spinbox);
442             }
443             else if(ext->ta.cursor.pos == (uint32_t)txt_len) {
444                 lv_textarea_set_cursor_pos(spinbox, txt_len - 1);
445             }
446             else if(ext->ta.cursor.pos == 0 && ext->range_min < 0) {
447                 lv_textarea_set_cursor_pos(spinbox, 1);
448             }
449 
450             size_t len = ext->digit_count - 1;
451             uint16_t cp = ext->ta.cursor.pos;
452 
453             if(ext->ta.cursor.pos > ext->dec_point_pos && ext->dec_point_pos != 0) cp--;
454             uint32_t pos = len - cp;
455 
456             if(ext->range_min < 0) pos++;
457 
458             ext->step = 1;
459             uint16_t i;
460             for(i = 0; i < pos; i++) ext->step *= 10;
461 
462 
463 
464         }
465     }
466     else if(sign == LV_SIGNAL_CONTROL) {
467 #if LV_USE_GROUP
468         lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
469 
470         uint32_t c = *((uint32_t *)param); /*uint32_t because can be UTF-8*/
471         if(c == LV_KEY_RIGHT) {
472             if(indev_type == LV_INDEV_TYPE_ENCODER)
473                 lv_spinbox_increment(spinbox);
474             else
475                 lv_spinbox_step_next(spinbox);
476         }
477         else if(c == LV_KEY_LEFT) {
478             if(indev_type == LV_INDEV_TYPE_ENCODER)
479                 lv_spinbox_decrement(spinbox);
480             else
481                 lv_spinbox_step_prev(spinbox);
482         }
483         else if(c == LV_KEY_UP) {
484             lv_spinbox_increment(spinbox);
485         }
486         else if(c == LV_KEY_DOWN) {
487             lv_spinbox_decrement(spinbox);
488         }
489         else {
490             lv_textarea_add_char(spinbox, c);
491         }
492 #endif
493     }
494 
495     return res;
496 }
497 
498 /**
499  * Get the style descriptor of a part of the object
500  * @param page pointer the object
501  * @param part the part from `lv_spinbox_part_t`. (LV_SPINBOX_PART_...)
502  * @return pointer to the style descriptor of the specified part
503  */
lv_spinbox_get_style(lv_obj_t * ta,uint8_t part)504 static lv_style_list_t * lv_spinbox_get_style(lv_obj_t * ta, uint8_t part)
505 {
506     LV_ASSERT_OBJ(ta, LV_OBJX_NAME);
507 
508     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(ta);
509     lv_style_list_t * style_dsc_p;
510 
511     switch(part) {
512         case LV_SPINBOX_PART_BG:
513             style_dsc_p = &ta->style_list;
514             break;
515         case LV_SPINBOX_PART_CURSOR:
516             style_dsc_p = &ext->ta.cursor.style;
517             break;
518         default:
519             style_dsc_p = NULL;
520     }
521 
522     return style_dsc_p;
523 }
lv_spinbox_updatevalue(lv_obj_t * spinbox)524 static void lv_spinbox_updatevalue(lv_obj_t * spinbox)
525 {
526     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
527 
528     char buf[LV_SPINBOX_MAX_DIGIT_COUNT + 8];
529     _lv_memset_00(buf, sizeof(buf));
530     char * buf_p = buf;
531     uint8_t cur_shift_left = 0;
532 
533     if(ext->range_min < 0) {  // hide sign if there are only positive values
534         /*Add the sign*/
535         (*buf_p) = ext->value >= 0 ? '+' : '-';
536         buf_p++;
537     }
538     else {
539         /*Cursor need shift to left*/
540         cur_shift_left++;
541     }
542 
543     int32_t i;
544     /*padding left*/
545     for(i = 0; i < ext->digit_padding_left; i++) {
546         (*buf_p) = ' ';
547         buf_p++;
548     }
549 
550     char digits[64];
551     /*Convert the numbers to string (the sign is already handled so always covert positive number)*/
552     _lv_utils_num_to_str(ext->value < 0 ? -ext->value : ext->value, digits);
553 
554     /*Add leading zeros*/
555     int lz_cnt = ext->digit_count - (int)strlen(digits);
556     if(lz_cnt > 0) {
557         for(i = (uint16_t)strlen(digits); i >= 0; i--) {
558             digits[i + lz_cnt] = digits[i];
559         }
560         for(i = 0; i < lz_cnt; i++) {
561             digits[i] = '0';
562         }
563     }
564 
565     int32_t intDigits;
566     intDigits = (ext->dec_point_pos == 0) ? ext->digit_count : ext->dec_point_pos;
567 
568     /*Add the decimal part*/
569     for(i = 0; i < intDigits && digits[i] != '\0'; i++) {
570         (*buf_p) = digits[i];
571         buf_p++;
572     }
573 
574     if(ext->dec_point_pos != 0) {
575         /*Insert the decimal point*/
576         (*buf_p) = '.';
577         buf_p++;
578 
579         for(/*Leave i*/; i < ext->digit_count && digits[i] != '\0'; i++) {
580             (*buf_p) = digits[i];
581             buf_p++;
582         }
583     }
584 
585     /*Refresh the text*/
586     lv_textarea_set_text(spinbox, (char *)buf);
587 
588     /*Set the cursor position*/
589     int32_t step    = ext->step;
590     uint8_t cur_pos = (uint8_t)ext->digit_count;
591     while(step >= 10) {
592         step /= 10;
593         cur_pos--;
594     }
595 
596     if(cur_pos > intDigits) cur_pos++; /*Skip the decimal point*/
597 
598     cur_pos += (ext->digit_padding_left - cur_shift_left);
599 
600     lv_textarea_set_cursor_pos(spinbox, cur_pos);
601 }
602 
603 #endif
604