1 /**
2  * @file lv_spinbox.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_spinbox.h"
10 #if LV_USE_SPINBOX
11 
12 #include "../../../misc/lv_assert.h"
13 
14 /*********************
15  *      DEFINES
16  *********************/
17 #define MY_CLASS    &lv_spinbox_class
18 
19 /**********************
20  *      TYPEDEFS
21  **********************/
22 
23 /**********************
24  *  STATIC PROTOTYPES
25  **********************/
26 
27 static void lv_spinbox_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
28 static void lv_spinbox_event(const lv_obj_class_t * class_p, lv_event_t * e);
29 static void lv_spinbox_updatevalue(lv_obj_t * obj);
30 
31 /**********************
32  *  STATIC VARIABLES
33  **********************/
34 const lv_obj_class_t lv_spinbox_class = {
35     .constructor_cb = lv_spinbox_constructor,
36     .event_cb = lv_spinbox_event,
37     .instance_size = sizeof(lv_spinbox_t),
38     .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
39     .base_class = &lv_textarea_class
40 };
41 /**********************
42  *      MACROS
43  **********************/
44 
45 /**********************
46  *   GLOBAL FUNCTIONS
47  **********************/
48 
lv_spinbox_create(lv_obj_t * parent)49 lv_obj_t * lv_spinbox_create(lv_obj_t * parent)
50 {
51     LV_LOG_INFO("begin");
52     lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
53     lv_obj_class_init_obj(obj);
54     return obj;
55 }
56 
57 /*=====================
58  * Setter functions
59  *====================*/
60 
61 /**
62  * Set spinbox value
63  * @param obj pointer to spinbox
64  * @param i value to be set
65  */
lv_spinbox_set_value(lv_obj_t * obj,int32_t i)66 void lv_spinbox_set_value(lv_obj_t * obj, int32_t i)
67 {
68     LV_ASSERT_OBJ(obj, MY_CLASS);
69     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
70 
71     if(i > spinbox->range_max) i = spinbox->range_max;
72     if(i < spinbox->range_min) i = spinbox->range_min;
73 
74     spinbox->value = i;
75 
76     lv_spinbox_updatevalue(obj);
77 }
78 
79 /**
80  * Set spinbox rollover function
81  * @param spinbox pointer to spinbox
82  * @param b true or false to enable or disable (default)
83  */
lv_spinbox_set_rollover(lv_obj_t * obj,bool b)84 void lv_spinbox_set_rollover(lv_obj_t * obj, bool b)
85 {
86     LV_ASSERT_OBJ(obj, MY_CLASS);
87     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
88 
89     spinbox->rollover = b;
90 }
91 
92 /**
93  * Set spinbox digit format (digit count and decimal format)
94  * @param spinbox pointer to spinbox
95  * @param digit_count number of digit excluding the decimal separator and the sign
96  * @param separator_position number of digit before the decimal point. If 0, decimal point is not
97  * shown
98  */
lv_spinbox_set_digit_format(lv_obj_t * obj,uint8_t digit_count,uint8_t separator_position)99 void lv_spinbox_set_digit_format(lv_obj_t * obj, uint8_t digit_count, uint8_t separator_position)
100 {
101     LV_ASSERT_OBJ(obj, MY_CLASS);
102     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
103 
104     if(digit_count > LV_SPINBOX_MAX_DIGIT_COUNT) digit_count = LV_SPINBOX_MAX_DIGIT_COUNT;
105 
106     if(separator_position >= digit_count) separator_position = 0;
107     if(separator_position > LV_SPINBOX_MAX_DIGIT_COUNT) separator_position = LV_SPINBOX_MAX_DIGIT_COUNT;
108 
109     if(digit_count < LV_SPINBOX_MAX_DIGIT_COUNT) {
110         int64_t max_val = lv_pow(10, digit_count);
111         if(spinbox->range_max > max_val - 1) spinbox->range_max = max_val - 1;
112         if(spinbox->range_min < - max_val  + 1) spinbox->range_min = - max_val  + 1;
113     }
114 
115     spinbox->digit_count   = digit_count;
116     spinbox->dec_point_pos = separator_position;
117 
118     lv_spinbox_updatevalue(obj);
119 }
120 
121 /**
122  * Set spinbox step
123  * @param spinbox pointer to spinbox
124  * @param step steps on increment/decrement
125  */
lv_spinbox_set_step(lv_obj_t * obj,uint32_t step)126 void lv_spinbox_set_step(lv_obj_t * obj, uint32_t step)
127 {
128     LV_ASSERT_OBJ(obj, MY_CLASS);
129     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
130 
131     spinbox->step = step;
132     lv_spinbox_updatevalue(obj);
133 }
134 
135 /**
136  * Set spinbox value range
137  * @param spinbox pointer to spinbox
138  * @param range_min maximum value, inclusive
139  * @param range_max minimum value, inclusive
140  */
lv_spinbox_set_range(lv_obj_t * obj,int32_t range_min,int32_t range_max)141 void lv_spinbox_set_range(lv_obj_t * obj, int32_t range_min, int32_t range_max)
142 {
143     LV_ASSERT_OBJ(obj, MY_CLASS);
144     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
145 
146     spinbox->range_max = range_max;
147     spinbox->range_min = range_min;
148 
149     if(spinbox->value > spinbox->range_max) spinbox->value = spinbox->range_max;
150     if(spinbox->value < spinbox->range_min) spinbox->value = spinbox->range_min;
151 
152     lv_spinbox_updatevalue(obj);
153 }
154 
155 /**
156  * Set cursor position to a specific digit for edition
157  * @param spinbox pointer to spinbox
158  * @param pos selected position in spinbox
159  */
lv_spinbox_set_pos(lv_obj_t * obj,uint8_t pos)160 void lv_spinbox_set_pos(lv_obj_t * obj, uint8_t pos)
161 {
162     LV_ASSERT_OBJ(obj, MY_CLASS);
163     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
164     int32_t step_limit;
165     step_limit       = LV_MAX(spinbox->range_max, (spinbox->range_min < 0 ? (-spinbox->range_min) : spinbox->range_min));
166     int32_t new_step = spinbox->step * lv_pow(10, pos);
167     if(pos <= 0) spinbox->step = 1;
168     else if(new_step <= step_limit) spinbox->step = new_step;
169 
170     lv_spinbox_updatevalue(obj);
171 }
172 
173 /**
174  * Set direction of digit step when clicking an encoder button while in editing mode
175  * @param spinbox pointer to spinbox
176  * @param direction the direction (LV_DIR_RIGHT or LV_DIR_LEFT)
177  */
lv_spinbox_set_digit_step_direction(lv_obj_t * obj,lv_dir_t direction)178 void lv_spinbox_set_digit_step_direction(lv_obj_t * obj, lv_dir_t direction)
179 {
180     LV_ASSERT_OBJ(obj, MY_CLASS);
181     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
182     spinbox->digit_step_dir = direction;
183 
184     lv_spinbox_updatevalue(obj);
185 }
186 /*=====================
187  * Getter functions
188  *====================*/
189 
190 /**
191  * Get the spinbox numeral value (user has to convert to float according to its digit format)
192  * @param obj pointer to spinbox
193  * @return value integer value of the spinbox
194  */
lv_spinbox_get_value(lv_obj_t * obj)195 int32_t lv_spinbox_get_value(lv_obj_t * obj)
196 {
197     LV_ASSERT_OBJ(obj, MY_CLASS);
198     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
199 
200     return spinbox->value;
201 }
202 /**
203  * Get the spinbox step value (user has to convert to float according to its digit format)
204  * @param obj pointer to spinbox
205  * @return value integer step value of the spinbox
206  */
lv_spinbox_get_step(lv_obj_t * obj)207 int32_t lv_spinbox_get_step(lv_obj_t * obj)
208 {
209     LV_ASSERT_OBJ(obj, MY_CLASS);
210     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
211 
212     return spinbox->step;
213 }
214 
215 /*=====================
216  * Other functions
217  *====================*/
218 
219 /**
220  * Select next lower digit for edition
221  * @param obj pointer to spinbox
222  */
lv_spinbox_step_next(lv_obj_t * obj)223 void lv_spinbox_step_next(lv_obj_t * obj)
224 {
225     LV_ASSERT_OBJ(obj, MY_CLASS);
226     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
227 
228     int32_t new_step = spinbox->step / 10;
229     if((new_step) > 0)
230         spinbox->step = new_step;
231     else
232         spinbox->step = 1;
233 
234     lv_spinbox_updatevalue(obj);
235 }
236 
237 /**
238  * Select next higher digit for edition
239  * @param obj pointer to spinbox
240  */
lv_spinbox_step_prev(lv_obj_t * obj)241 void lv_spinbox_step_prev(lv_obj_t * obj)
242 {
243     LV_ASSERT_OBJ(obj, MY_CLASS);
244     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
245     int32_t step_limit;
246     step_limit       = LV_MAX(spinbox->range_max, (spinbox->range_min < 0 ? (-spinbox->range_min) : spinbox->range_min));
247     int32_t new_step = spinbox->step * 10;
248     if(new_step <= step_limit) spinbox->step = new_step;
249 
250     lv_spinbox_updatevalue(obj);
251 }
252 
253 /**
254  * Get spinbox rollover function status
255  * @param obj pointer to spinbox
256  */
lv_spinbox_get_rollover(lv_obj_t * obj)257 bool lv_spinbox_get_rollover(lv_obj_t * obj)
258 {
259     LV_ASSERT_OBJ(obj, MY_CLASS);
260     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
261 
262     return spinbox->rollover;
263 }
264 
265 /**
266  * Increment spinbox value by one step
267  * @param obj pointer to spinbox
268  */
lv_spinbox_increment(lv_obj_t * obj)269 void lv_spinbox_increment(lv_obj_t * obj)
270 {
271     LV_ASSERT_OBJ(obj, MY_CLASS);
272     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
273 
274     if(spinbox->value + spinbox->step <= spinbox->range_max) {
275         /*Special mode when zero crossing*/
276         if((spinbox->value + spinbox->step) > 0 && spinbox->value < 0) spinbox->value = -spinbox->value;
277         spinbox->value += spinbox->step;
278 
279     }
280     else {
281         // Rollover?
282         if((spinbox->rollover) && (spinbox->value == spinbox->range_max))
283             spinbox->value = spinbox->range_min;
284         else
285             spinbox->value = spinbox->range_max;
286     }
287 
288     lv_spinbox_updatevalue(obj);
289 }
290 
291 /**
292  * Decrement spinbox value by one step
293  * @param obj pointer to spinbox
294  */
lv_spinbox_decrement(lv_obj_t * obj)295 void lv_spinbox_decrement(lv_obj_t * obj)
296 {
297     LV_ASSERT_OBJ(obj, MY_CLASS);
298     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
299 
300     if(spinbox->value - spinbox->step >= spinbox->range_min) {
301         /*Special mode when zero crossing*/
302         if((spinbox->value - spinbox->step) < 0 && spinbox->value > 0) spinbox->value = -spinbox->value;
303         spinbox->value -= spinbox->step;
304     }
305     else {
306         /*Rollover?*/
307         if((spinbox->rollover) && (spinbox->value == spinbox->range_min))
308             spinbox->value = spinbox->range_max;
309         else
310             spinbox->value = spinbox->range_min;
311     }
312 
313     lv_spinbox_updatevalue(obj);
314 }
315 
316 /**********************
317  *   STATIC FUNCTIONS
318  **********************/
319 
lv_spinbox_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)320 static void lv_spinbox_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
321 {
322     LV_UNUSED(class_p);
323     LV_LOG_TRACE("begin");
324 
325     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
326 
327     /*Initialize the allocated 'ext'*/
328     spinbox->value              = 0;
329     spinbox->dec_point_pos      = 0;
330     spinbox->digit_count        = 5;
331     spinbox->step               = 1;
332     spinbox->range_max          = 99999;
333     spinbox->range_min          = -99999;
334     spinbox->rollover           = false;
335     spinbox->digit_step_dir     = LV_DIR_RIGHT;
336 
337     lv_textarea_set_one_line(obj, true);
338     lv_textarea_set_cursor_click_pos(obj, true);
339     lv_obj_set_width(obj, LV_DPI_DEF);
340 
341     lv_spinbox_updatevalue(obj);
342 
343     LV_LOG_TRACE("Spinbox constructor finished");
344 }
345 
lv_spinbox_event(const lv_obj_class_t * class_p,lv_event_t * e)346 static void lv_spinbox_event(const lv_obj_class_t * class_p, lv_event_t * e)
347 {
348     LV_UNUSED(class_p);
349 
350     /*Call the ancestor's event handler*/
351     lv_res_t res = LV_RES_OK;
352     res = lv_obj_event_base(MY_CLASS, e);
353     if(res != LV_RES_OK) return;
354 
355     lv_event_code_t code = lv_event_get_code(e);
356     lv_obj_t * obj = lv_event_get_target(e);
357     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
358     if(code == LV_EVENT_RELEASED) {
359         /*If released with an ENCODER then move to the next digit*/
360         lv_indev_t * indev = lv_indev_get_act();
361         if(lv_indev_get_type(indev) == LV_INDEV_TYPE_ENCODER) {
362             if(lv_group_get_editing(lv_obj_get_group(obj))) {
363                 if(spinbox->digit_count > 1) {
364                     if(spinbox->digit_step_dir == LV_DIR_RIGHT) {
365                         if(spinbox->step > 1) {
366                             lv_spinbox_step_next(obj);
367                         }
368                         else {
369                             /*Restart from the MSB*/
370                             spinbox->step = lv_pow(10, spinbox->digit_count - 2);
371                             lv_spinbox_step_prev(obj);
372                         }
373                     }
374                     else {
375                         if(spinbox->step < lv_pow(10, spinbox->digit_count - 1)) {
376                             lv_spinbox_step_prev(obj);
377                         }
378                         else {
379                             /*Restart from the LSB*/
380                             spinbox->step = 10;
381                             lv_spinbox_step_next(obj);
382                         }
383                     }
384                 }
385             }
386         }
387         /*The cursor has been positioned to a digit.
388          * Set `step` accordingly*/
389         else {
390             const char * txt = lv_textarea_get_text(obj);
391             size_t txt_len = strlen(txt);
392 
393             if(txt[spinbox->ta.cursor.pos] == '.') {
394                 lv_textarea_cursor_left(obj);
395             }
396             else if(spinbox->ta.cursor.pos == (uint32_t)txt_len) {
397                 lv_textarea_set_cursor_pos(obj, txt_len - 1);
398             }
399             else if(spinbox->ta.cursor.pos == 0 && spinbox->range_min < 0) {
400                 lv_textarea_set_cursor_pos(obj, 1);
401             }
402 
403             size_t len = spinbox->digit_count - 1;
404             uint16_t cp = spinbox->ta.cursor.pos;
405 
406             if(spinbox->ta.cursor.pos > spinbox->dec_point_pos && spinbox->dec_point_pos != 0) cp--;
407             uint32_t pos = len - cp;
408 
409             if(spinbox->range_min < 0) pos++;
410 
411             spinbox->step = 1;
412             uint16_t i;
413             for(i = 0; i < pos; i++) spinbox->step *= 10;
414         }
415     }
416     else if(code == LV_EVENT_KEY) {
417         lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
418 
419         uint32_t c = *((uint32_t *)lv_event_get_param(e)); /*uint32_t because can be UTF-8*/
420         if(c == LV_KEY_RIGHT) {
421             if(indev_type == LV_INDEV_TYPE_ENCODER)
422                 lv_spinbox_increment(obj);
423             else
424                 lv_spinbox_step_next(obj);
425         }
426         else if(c == LV_KEY_LEFT) {
427             if(indev_type == LV_INDEV_TYPE_ENCODER)
428                 lv_spinbox_decrement(obj);
429             else
430                 lv_spinbox_step_prev(obj);
431         }
432         else if(c == LV_KEY_UP) {
433             lv_spinbox_increment(obj);
434         }
435         else if(c == LV_KEY_DOWN) {
436             lv_spinbox_decrement(obj);
437         }
438         else {
439             lv_textarea_add_char(obj, c);
440         }
441     }
442 }
443 
lv_spinbox_updatevalue(lv_obj_t * obj)444 static void lv_spinbox_updatevalue(lv_obj_t * obj)
445 {
446     lv_spinbox_t * spinbox = (lv_spinbox_t *)obj;
447 
448     char buf[LV_SPINBOX_MAX_DIGIT_COUNT + 8];
449     lv_memset_00(buf, sizeof(buf));
450     char * buf_p = buf;
451     uint8_t cur_shift_left = 0;
452 
453     if(spinbox->range_min < 0) {  // hide sign if there are only positive values
454         /*Add the sign*/
455         (*buf_p) = spinbox->value >= 0 ? '+' : '-';
456         buf_p++;
457     }
458     else {
459         /*Cursor need shift to left*/
460         cur_shift_left++;
461     }
462 
463     int32_t i;
464     char digits[LV_SPINBOX_MAX_DIGIT_COUNT + 4];
465     /*Convert the numbers to string (the sign is already handled so always covert positive number)*/
466     lv_snprintf(digits, sizeof(digits), "%" LV_PRId32, LV_ABS(spinbox->value));
467 
468     /*Add leading zeros*/
469     int lz_cnt = spinbox->digit_count - (int)strlen(digits);
470     if(lz_cnt > 0) {
471         for(i = (uint16_t)strlen(digits); i >= 0; i--) {
472             digits[i + lz_cnt] = digits[i];
473         }
474         for(i = 0; i < lz_cnt; i++) {
475             digits[i] = '0';
476         }
477     }
478 
479     int32_t intDigits;
480     intDigits = (spinbox->dec_point_pos == 0) ? spinbox->digit_count : spinbox->dec_point_pos;
481 
482     /*Add the decimal part*/
483     for(i = 0; i < intDigits && digits[i] != '\0'; i++) {
484         (*buf_p) = digits[i];
485         buf_p++;
486     }
487 
488     if(spinbox->dec_point_pos != 0) {
489         /*Insert the decimal point*/
490         (*buf_p) = '.';
491         buf_p++;
492 
493         for(/*Leave i*/; i < spinbox->digit_count && digits[i] != '\0'; i++) {
494             (*buf_p) = digits[i];
495             buf_p++;
496         }
497     }
498 
499     /*Refresh the text*/
500     lv_textarea_set_text(obj, (char *)buf);
501 
502     /*Set the cursor position*/
503     int32_t step    = spinbox->step;
504     uint8_t cur_pos = (uint8_t)spinbox->digit_count;
505     while(step >= 10) {
506         step /= 10;
507         cur_pos--;
508     }
509 
510     if(cur_pos > intDigits) cur_pos++; /*Skip the decimal point*/
511 
512     cur_pos -= cur_shift_left;
513 
514     lv_textarea_set_cursor_pos(obj, cur_pos);
515 }
516 
517 #endif /*LV_USE_SPINBOX*/
518