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_themes/lv_theme.h"
13 #include "../lv_misc/lv_math.h"
14 #include "../lv_misc/lv_utils.h"
15 
16 /*********************
17  *      DEFINES
18  *********************/
19 
20 /**********************
21  *      TYPEDEFS
22  **********************/
23 
24 /**********************
25  *  STATIC PROTOTYPES
26  **********************/
27 static lv_res_t lv_spinbox_signal(lv_obj_t * spinbox, lv_signal_t sign, void * param);
28 static void lv_spinbox_updatevalue(lv_obj_t * spinbox);
29 
30 /**********************
31  *  STATIC VARIABLES
32  **********************/
33 static lv_signal_cb_t ancestor_signal;
34 static lv_design_cb_t ancestor_design;
35 
36 /**********************
37  *      MACROS
38  **********************/
39 
40 /**********************
41  *   GLOBAL FUNCTIONS
42  **********************/
43 
44 /**
45  * Create a spinbox object
46  * @param par pointer to an object, it will be the parent of the new spinbox
47  * @param copy pointer to a spinbox object, if not NULL then the new object will be copied from it
48  * @return pointer to the created spinbox
49  */
lv_spinbox_create(lv_obj_t * par,const lv_obj_t * copy)50 lv_obj_t * lv_spinbox_create(lv_obj_t * par, const lv_obj_t * copy)
51 {
52     LV_LOG_TRACE("spinbox create started");
53 
54     /*Create the ancestor of spinbox*/
55     lv_obj_t * new_spinbox = lv_ta_create(par, copy);
56     lv_mem_assert(new_spinbox);
57     if(new_spinbox == NULL) return NULL;
58 
59     /*Allocate the spinbox type specific extended data*/
60     lv_spinbox_ext_t * ext = lv_obj_allocate_ext_attr(new_spinbox, sizeof(lv_spinbox_ext_t));
61     lv_mem_assert(ext);
62     if(ext == NULL) return NULL;
63     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(new_spinbox);
64     if(ancestor_design == NULL) ancestor_design = lv_obj_get_design_cb(new_spinbox);
65 
66     /*Initialize the allocated 'ext'*/
67     ext->value              = 0;
68     ext->dec_point_pos      = 0;
69     ext->digit_count        = 5;
70     ext->digit_padding_left = 0;
71     ext->step               = 1;
72     ext->range_max          = 99999;
73     ext->range_min          = -99999;
74 
75     lv_ta_set_cursor_type(new_spinbox, LV_CURSOR_BLOCK);
76     lv_ta_set_one_line(new_spinbox, true);
77     lv_ta_set_cursor_click_pos(new_spinbox, false);
78 
79     /*The signal and design functions are not copied so set them here*/
80     lv_obj_set_signal_cb(new_spinbox, lv_spinbox_signal);
81     lv_obj_set_design_cb(new_spinbox, ancestor_design); /*Leave the Text area's design function*/
82 
83     /*Init the new spinbox spinbox*/
84     if(copy == NULL) {
85         /*Set the default styles*/
86         lv_theme_t * th = lv_theme_get_current();
87         if(th) {
88             lv_spinbox_set_style(new_spinbox, LV_SPINBOX_STYLE_BG, th->style.spinbox.bg);
89             lv_spinbox_set_style(new_spinbox, LV_SPINBOX_STYLE_CURSOR, th->style.spinbox.cursor);
90             lv_spinbox_set_style(new_spinbox, LV_SPINBOX_STYLE_SB, th->style.spinbox.sb);
91         }
92     }
93     /*Copy an existing spinbox*/
94     else {
95         lv_spinbox_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
96 
97         lv_spinbox_set_value(new_spinbox, copy_ext->value);
98         lv_spinbox_set_digit_format(new_spinbox, copy_ext->digit_count, copy_ext->dec_point_pos);
99         lv_spinbox_set_range(new_spinbox, copy_ext->range_min, copy_ext->range_max);
100         lv_spinbox_set_step(new_spinbox, copy_ext->step);
101 
102         /*Refresh the style with new signal function*/
103         lv_obj_refresh_style(new_spinbox);
104     }
105 
106     lv_spinbox_updatevalue(new_spinbox);
107 
108     LV_LOG_INFO("spinbox created");
109 
110     return new_spinbox;
111 }
112 
113 /*=====================
114  * Setter functions
115  *====================*/
116 
117 /**
118  * Set spinbox value
119  * @param spinbox pointer to spinbox
120  * @param i value to be set
121  */
lv_spinbox_set_value(lv_obj_t * spinbox,int32_t i)122 void lv_spinbox_set_value(lv_obj_t * spinbox, int32_t i)
123 {
124     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
125     if(ext == NULL) return;
126 
127     if(i > ext->range_max) i = ext->range_max;
128     if(i < ext->range_min) i = ext->range_min;
129 
130     ext->value = i;
131 
132     lv_spinbox_updatevalue(spinbox);
133 }
134 
135 /**
136  * Set spinbox digit format (digit count and decimal format)
137  * @param spinbox pointer to spinbox
138  * @param digit_count number of digit excluding the decimal separator and the sign
139  * @param separator_position number of digit before the decimal point. If 0, decimal point is not
140  * shown
141  */
lv_spinbox_set_digit_format(lv_obj_t * spinbox,uint8_t digit_count,uint8_t separator_position)142 void lv_spinbox_set_digit_format(lv_obj_t * spinbox, uint8_t digit_count, uint8_t separator_position)
143 {
144     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
145     if(ext == NULL) return;
146 
147     if(digit_count > LV_SPINBOX_MAX_DIGIT_COUNT) digit_count = LV_SPINBOX_MAX_DIGIT_COUNT;
148 
149     if(separator_position > LV_SPINBOX_MAX_DIGIT_COUNT) separator_position = LV_SPINBOX_MAX_DIGIT_COUNT;
150 
151     ext->digit_count   = digit_count;
152     ext->dec_point_pos = separator_position;
153 
154     lv_spinbox_updatevalue(spinbox);
155 }
156 
157 /**
158  * Set spinbox step
159  * @param spinbox pointer to spinbox
160  * @param step steps on increment/decrement
161  */
lv_spinbox_set_step(lv_obj_t * spinbox,uint32_t step)162 void lv_spinbox_set_step(lv_obj_t * spinbox, uint32_t step)
163 {
164     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
165     if(ext == NULL) return;
166 
167     ext->step = step;
168 }
169 
170 /**
171  * Set spinbox value range
172  * @param spinbox pointer to spinbox
173  * @param range_min maximum value, inclusive
174  * @param range_max minimum value, inclusive
175  */
lv_spinbox_set_range(lv_obj_t * spinbox,int32_t range_min,int32_t range_max)176 void lv_spinbox_set_range(lv_obj_t * spinbox, int32_t range_min, int32_t range_max)
177 {
178     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
179     if(ext == NULL) return;
180 
181     ext->range_max = range_max;
182     ext->range_min = range_min;
183 
184     if(ext->value > ext->range_max) {
185         ext->value = ext->range_max;
186         lv_obj_invalidate(spinbox);
187     }
188     if(ext->value < ext->range_min) {
189         ext->value = ext->range_min;
190         lv_obj_invalidate(spinbox);
191     }
192 }
193 
194 /**
195  * Set spinbox left padding in digits count (added between sign and first digit)
196  * @param spinbox pointer to spinbox
197  * @param cb Callback function called on value change event
198  */
lv_spinbox_set_padding_left(lv_obj_t * spinbox,uint8_t padding)199 void lv_spinbox_set_padding_left(lv_obj_t * spinbox, uint8_t padding)
200 {
201     lv_spinbox_ext_t * ext  = lv_obj_get_ext_attr(spinbox);
202     ext->digit_padding_left = padding;
203     lv_spinbox_updatevalue(spinbox);
204 }
205 
206 /*=====================
207  * Getter functions
208  *====================*/
209 
210 /**
211  * Get the spinbox numeral value (user has to convert to float according to its digit format)
212  * @param spinbox pointer to spinbox
213  * @return value integer value of the spinbox
214  */
lv_spinbox_get_value(lv_obj_t * spinbox)215 int32_t lv_spinbox_get_value(lv_obj_t * spinbox)
216 {
217     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
218 
219     return ext->value;
220 }
221 
222 /*=====================
223  * Other functions
224  *====================*/
225 
226 /**
227  * Select next lower digit for edition
228  * @param spinbox pointer to spinbox
229  */
lv_spinbox_step_next(lv_obj_t * spinbox)230 void lv_spinbox_step_next(lv_obj_t * spinbox)
231 {
232     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
233 
234     int32_t new_step = ext->step / 10;
235     if((new_step) > 0)
236         ext->step = new_step;
237     else
238         ext->step = 1;
239 
240     lv_spinbox_updatevalue(spinbox);
241 }
242 
243 /**
244  * Select next higher digit for edition
245  * @param spinbox pointer to spinbox
246  */
lv_spinbox_step_prev(lv_obj_t * spinbox)247 void lv_spinbox_step_prev(lv_obj_t * spinbox)
248 {
249     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
250     int32_t step_limit;
251     step_limit       = LV_MATH_MAX(ext->range_max, (ext->range_min < 0 ? (-ext->range_min) : ext->range_min));
252     int32_t new_step = ext->step * 10;
253     if(new_step <= step_limit) ext->step = new_step;
254 
255     lv_spinbox_updatevalue(spinbox);
256 }
257 
258 /**
259  * Increment spinbox value by one step
260  * @param spinbox pointer to spinbox
261  */
lv_spinbox_increment(lv_obj_t * spinbox)262 void lv_spinbox_increment(lv_obj_t * spinbox)
263 {
264     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
265 
266     if(ext->value + ext->step <= ext->range_max) {
267         /*Special mode when zero crossing*/
268         if((ext->value + ext->step) > 0 && ext->value < 0) ext->value = -ext->value;
269         ext->value += ext->step;
270 
271     } else {
272         ext->value = ext->range_max;
273     }
274 
275     lv_spinbox_updatevalue(spinbox);
276 }
277 
278 /**
279  * Decrement spinbox value by one step
280  * @param spinbox pointer to spinbox
281  */
lv_spinbox_decrement(lv_obj_t * spinbox)282 void lv_spinbox_decrement(lv_obj_t * spinbox)
283 {
284     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
285 
286     if(ext->value - ext->step >= ext->range_min) {
287         /*Special mode when zero crossing*/
288         if((ext->value - ext->step) < 0 && ext->value > 0) ext->value = -ext->value;
289         ext->value -= ext->step;
290     } else {
291         ext->value = ext->range_min;
292     }
293 
294     lv_spinbox_updatevalue(spinbox);
295 }
296 
297 /**********************
298  *   STATIC FUNCTIONS
299  **********************/
300 
301 /**
302  * Signal function of the spinbox
303  * @param spinbox pointer to a spinbox object
304  * @param sign a signal type from lv_signal_t enum
305  * @param param pointer to a signal specific variable
306  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
307  */
lv_spinbox_signal(lv_obj_t * spinbox,lv_signal_t sign,void * param)308 static lv_res_t lv_spinbox_signal(lv_obj_t * spinbox, lv_signal_t sign, void * param)
309 {
310 
311     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
312 
313     lv_res_t res = LV_RES_OK;
314 
315     /* Include the ancient signal function */
316     if(sign != LV_SIGNAL_CONTROL) {
317         res = ancestor_signal(spinbox, sign, param);
318         if(res != LV_RES_OK) return res;
319     }
320 
321     if(sign == LV_SIGNAL_CLEANUP) {
322         /*Nothing to cleanup. (No dynamically allocated memory in 'ext')*/
323     } else if(sign == LV_SIGNAL_GET_TYPE) {
324         lv_obj_type_t * buf = param;
325         uint8_t i;
326         for(i = 0; i < LV_MAX_ANCESTOR_NUM - 1; i++) { /*Find the last set data*/
327             if(buf->type[i] == NULL) break;
328         }
329         buf->type[i] = "lv_spinbox";
330     } else if(sign == LV_SIGNAL_RELEASED) {
331         /*If released with an ENCODER then move to the next digit*/
332 #if LV_USE_GROUP
333         lv_indev_t * indev = lv_indev_get_act();
334         if(lv_indev_get_type(indev) == LV_INDEV_TYPE_ENCODER) {
335             if(lv_group_get_editing(lv_obj_get_group(spinbox))) {
336                 if(ext->step > 1) {
337                     lv_spinbox_step_next(spinbox);
338                 } else {
339                     /*Restart from the MSB*/
340                     ext->step = 1;
341                     uint32_t i;
342                     for(i = 0; i < ext->digit_count; i++) {
343                         int32_t new_step = ext->step * 10;
344                         if(new_step >= ext->range_max) break;
345                         ext->step = new_step;
346                     }
347                     lv_spinbox_step_prev(spinbox);
348                 }
349             }
350         }
351 #endif
352     } else if(sign == LV_SIGNAL_CONTROL) {
353         lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
354 
355         uint32_t c = *((uint32_t *)param); /*uint32_t because can be UTF-8*/
356         if(c == LV_KEY_RIGHT) {
357             if(indev_type == LV_INDEV_TYPE_ENCODER)
358                 lv_spinbox_increment(spinbox);
359             else
360                 lv_spinbox_step_next(spinbox);
361         } else if(c == LV_KEY_LEFT) {
362             if(indev_type == LV_INDEV_TYPE_ENCODER)
363                 lv_spinbox_decrement(spinbox);
364             else
365                 lv_spinbox_step_prev(spinbox);
366         } else if(c == LV_KEY_UP) {
367             lv_spinbox_increment(spinbox);
368         } else if(c == LV_KEY_DOWN) {
369             lv_spinbox_decrement(spinbox);
370         } else {
371             lv_ta_add_char(spinbox, c);
372         }
373     }
374 
375     return res;
376 }
377 
lv_spinbox_updatevalue(lv_obj_t * spinbox)378 static void lv_spinbox_updatevalue(lv_obj_t * spinbox)
379 {
380     lv_spinbox_ext_t * ext = lv_obj_get_ext_attr(spinbox);
381 
382     char buf[LV_SPINBOX_MAX_DIGIT_COUNT + 8];
383     memset(buf, 0, sizeof(buf));
384     char * buf_p = buf;
385 
386     /*Add the sign*/
387     (*buf_p) = ext->value >= 0 ? '+' : '-';
388     buf_p++;
389 
390     int i;
391     /*padding left*/
392     for(i = 0; i < ext->digit_padding_left; i++) {
393         (*buf_p) = ' ';
394         buf_p++;
395     }
396 
397     char digits[64];
398     /*Convert the numbers to string (the sign is already handled so always covert positive number)*/
399     lv_utils_num_to_str(ext->value < 0 ? -ext->value : ext->value, digits);
400 
401     /*Add leading zeros*/
402     int lz_cnt = ext->digit_count - (int)strlen(digits);
403     if(lz_cnt > 0) {
404         for(i = strlen(digits); i >= 0; i--) {
405             digits[i + lz_cnt] = digits[i];
406         }
407         for(i = 0; i < lz_cnt; i++) {
408             digits[i] = '0';
409         }
410     }
411 
412     int32_t intDigits;
413     intDigits = (ext->dec_point_pos == 0) ? ext->digit_count : ext->dec_point_pos;
414 
415     /*Add the decimal part*/
416     for(i = 0; i < intDigits && digits[i] != '\0'; i++) {
417         (*buf_p) = digits[i];
418         buf_p++;
419     }
420 
421     if(ext->dec_point_pos != 0) {
422         /*Insert the decimal point*/
423         (*buf_p) = '.';
424         buf_p++;
425 
426         for(/*Leave i*/; i < ext->digit_count && digits[i] != '\0'; i++) {
427             (*buf_p) = digits[i];
428             buf_p++;
429         }
430     }
431 
432     /*Refresh the text*/
433     lv_ta_set_text(spinbox, (char *)buf);
434 
435     /*Set the cursor position*/
436     int32_t step    = ext->step;
437     uint8_t cur_pos = ext->digit_count;
438     while(step >= 10) {
439         step /= 10;
440         cur_pos--;
441     }
442 
443     if(cur_pos > intDigits) cur_pos++; /*Skip teh decimal point*/
444 
445     cur_pos += ext->digit_padding_left;
446 
447     lv_ta_set_cursor_pos(spinbox, cur_pos);
448 }
449 
450 #endif
451