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