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