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