1 /**
2  * \file
3  *
4  * \brief Spin control widget
5  * Copyright (c) 2011-2015 Atmel Corporation. All rights reserved.
6  *
7  * \asf_license_start
8  *
9  * \page License
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions are met:
13  *
14  * 1. Redistributions of source code must retain the above copyright notice,
15  *    this list of conditions and the following disclaimer.
16  *
17  * 2. Redistributions in binary form must reproduce the above copyright notice,
18  *    this list of conditions and the following disclaimer in the documentation
19  *    and/or other materials provided with the distribution.
20  *
21  * 3. The name of Atmel may not be used to endorse or promote products derived
22  *    from this software without specific prior written permission.
23  *
24  * 4. This software may only be redistributed and used in connection with an
25  *    Atmel microcontroller product.
26  *
27  * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
28  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
29  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
30  * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
31  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
36  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  *
39  * \asf_license_stop
40  *
41  */
42 /*
43  * Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
44  */
45 
46 #include "sysfont.h"
47 #include "stdio.h"
48 #include "gfx_mono.h"
49 #include "gfx_mono_spinctrl.h"
50 
51 PROGMEM_DECLARE(gfx_mono_color_t, spin_indicator_data[]) = {
52 	GFX_MONO_SPINCTRL_SPIN_INDICATOR_BITMAP
53 };
54 
55 PROGMEM_DECLARE(gfx_mono_color_t, indicator_data[]) = {
56 	GFX_MONO_SPINCTRL_INDICATOR_BITMAP
57 };
58 
59 struct gfx_mono_bitmap gfx_mono_spinctrl_bitmap_spin_indicator = {
60 	.height = GFX_MONO_SPINCTRL_SPIN_INDICATOR_HEIGHT,
61 	.width = GFX_MONO_SPINCTRL_SPIN_INDICATOR_WIDTH,
62 	.type = GFX_MONO_BITMAP_PROGMEM,
63 	.data.progmem = spin_indicator_data
64 };
65 
66 struct gfx_mono_bitmap gfx_mono_spinctrl_bitmap_indicator = {
67 	.height = GFX_MONO_SPINCTRL_INDICATOR_HEIGHT,
68 	.width = GFX_MONO_SPINCTRL_INDICATOR_WIDTH,
69 	.type = GFX_MONO_BITMAP_PROGMEM,
70 	.data.progmem = indicator_data
71 };
72 
73 /**
74  * \brief Draw or delete indicator arrow in front of spinner
75  *
76  * \param[in] *spinner initialized gfx_mono_spinctrl struct
77  * \param[in] draw     true on draw, false on delete
78  */
gfx_mono_spinctrl_draw_indicator(struct gfx_mono_spinctrl * spinner,bool draw)79 static void gfx_mono_spinctrl_draw_indicator(struct gfx_mono_spinctrl *spinner,
80 		bool draw)
81 {
82 	if (draw) {
83 		gfx_mono_put_bitmap(&gfx_mono_spinctrl_bitmap_indicator, 0,
84 				spinner->y);
85 	} else {
86 		gfx_mono_draw_filled_rect(0, spinner->y,
87 				GFX_MONO_SPINCTRL_INDICATOR_WIDTH,
88 				GFX_MONO_SPINCTRL_INDICATOR_HEIGHT,
89 				GFX_PIXEL_CLR);
90 	}
91 }
92 
93 /**
94  * \brief Draw or delete indicator in front of spinner data
95  *
96  * \param[in] spinner  pointer to initialized gfx_mono_spinctrl struct
97  * \param[in] draw     true on draw, false on delete
98  */
gfx_mono_spinctrl_draw_spin_indicator(struct gfx_mono_spinctrl * spinner,bool draw)99 static void gfx_mono_spinctrl_draw_spin_indicator(struct gfx_mono_spinctrl
100 		*spinner, bool draw)
101 {
102 	if (draw) {
103 		gfx_mono_put_bitmap(&gfx_mono_spinctrl_bitmap_spin_indicator,
104 				GFX_MONO_LCD_WIDTH - GFX_MONO_SPINCTRL_SPIN_INDICATOR_WIDTH,
105 				spinner->y);
106 	} else {
107 		gfx_mono_draw_filled_rect(GFX_MONO_LCD_WIDTH -
108 				GFX_MONO_SPINCTRL_SPIN_INDICATOR_WIDTH,
109 				spinner->y,
110 				GFX_MONO_SPINCTRL_SPIN_INDICATOR_WIDTH,
111 				GFX_MONO_SPINCTRL_SPIN_INDICATOR_HEIGHT,
112 				GFX_PIXEL_CLR);
113 	}
114 }
115 
116 /**
117  * \brief Draw OK button at bottom of screen
118  *
119  * This function draws an OK button at the bottom of the screen. It will
120  * also draw an indicator arrow in front of the button if the indicator bool
121  * is true. If the draw bool is false, the OK button will be deleted, and if
122  * the indicator bool is false, the indicator will be deleted.
123  *
124  * \param[in] draw       true on draw, false on delete
125  * \param[in] indicator  true on draw indicator, false on delete
126  */
gfx_mono_spinctrl_draw_button(bool draw,bool indicator)127 static void gfx_mono_spinctrl_draw_button(bool draw, bool indicator)
128 {
129 	uint8_t width;
130 	uint8_t height;
131 	uint8_t offset;
132 	char string_buf[22];
133 
134 	/* Clear bottom line */
135 	gfx_mono_draw_filled_rect(0,
136 			(SYSFONT_HEIGHT + 1) *
137 			GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION,
138 			GFX_MONO_LCD_WIDTH, SYSFONT_HEIGHT, GFX_PIXEL_CLR);
139 
140 	snprintf(string_buf, sizeof(string_buf), "OK");
141 	gfx_mono_get_string_bounding_box(string_buf, &sysfont, &width, &height);
142 	offset = (GFX_MONO_LCD_WIDTH - width) / 2;
143 
144 	if (draw) {
145 		/* Draw OK button in the middle of the last line */
146 		gfx_mono_draw_string(string_buf, offset,
147 				(SYSFONT_HEIGHT + 1) *
148 				GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION,
149 				&sysfont);
150 		if (indicator) {
151 			/* Draw indicator arrow in front of button */
152 			gfx_mono_put_bitmap(&gfx_mono_spinctrl_bitmap_indicator,
153 					offset - GFX_MONO_SPINCTRL_INDICATOR_WIDTH,
154 					(SYSFONT_HEIGHT + 1) *
155 					GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION);
156 		} else {
157 			/* Delete indicator */
158 			gfx_mono_draw_filled_rect(offset -
159 					GFX_MONO_SPINCTRL_INDICATOR_WIDTH,
160 					(SYSFONT_HEIGHT + 1) *
161 					GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION,
162 					GFX_MONO_SPINCTRL_INDICATOR_WIDTH,
163 					GFX_MONO_SPINCTRL_INDICATOR_HEIGHT,
164 					GFX_PIXEL_CLR);
165 		}
166 	} else {
167 		/* Delete OK button */
168 		gfx_mono_draw_filled_rect(
169 				offset - GFX_MONO_SPINCTRL_INDICATOR_WIDTH,
170 				(SYSFONT_HEIGHT + 1) *
171 				GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION, 20,
172 				SYSFONT_HEIGHT, GFX_PIXEL_CLR);
173 	}
174 }
175 
176 /**
177  * \brief Draw spinner at its position.
178  *
179  * This function draws a spinner at its position.
180  * The title of the spinner is only drawn if the redraw option is set.
181  * If the spinner is in focus, arrows will be drawn next to the spinner value
182  * to indicate that it is spinnable.
183  *
184  * \param[in] spinner  pointer to initialized gfx_mono_spinctrl struct
185  * \param[in] redraw   true if title of spinner should be drawn
186  */
gfx_mono_spinctrl_draw(struct gfx_mono_spinctrl * spinner,bool redraw)187 void gfx_mono_spinctrl_draw(struct gfx_mono_spinctrl *spinner, bool redraw)
188 {
189 	char string_buf[GFX_MONO_SPINCTRL_INT_SPINNER_WIDTH];
190 	uint8_t index;
191 	uint8_t offset;
192 
193 	if (redraw) {
194 		/* Clear line */
195 		gfx_mono_draw_filled_rect(0, spinner->y, GFX_MONO_LCD_WIDTH,
196 				SYSFONT_HEIGHT, GFX_PIXEL_CLR);
197 		/* Draw title */
198 		gfx_mono_draw_progmem_string((char PROGMEM_PTR_T)spinner->title,
199 				GFX_MONO_SPINCTRL_INDICATOR_WIDTH + 1,
200 				spinner->y, &sysfont);
201 	}
202 
203 	if (spinner->in_focus) {
204 		gfx_mono_spinctrl_draw_spin_indicator(spinner, true);
205 	} else {
206 		gfx_mono_spinctrl_draw_spin_indicator(spinner, false);
207 	}
208 
209 	if (spinner->datatype == SPINTYPE_INTEGER) {
210 		offset = GFX_MONO_LCD_WIDTH -
211 				(SYSFONT_WIDTH *
212 				GFX_MONO_SPINCTRL_INT_SPINNER_WIDTH);
213 		snprintf(string_buf, sizeof(string_buf), "%d",
214 				spinner->integer_data);
215 		/* Delete previous spinner data */
216 		gfx_mono_draw_filled_rect(offset, spinner->y,
217 				GFX_MONO_LCD_WIDTH - offset -
218 				GFX_MONO_SPINCTRL_SPIN_INDICATOR_WIDTH,
219 				SYSFONT_HEIGHT,
220 				GFX_PIXEL_CLR);
221 		/* Draw integer data */
222 		gfx_mono_draw_string(string_buf, offset, spinner->y, &sysfont);
223 	} else if (spinner->datatype == SPINTYPE_STRING) {
224 		index = spinner->strings.index;
225 		offset = GFX_MONO_LCD_WIDTH -
226 				(SYSFONT_WIDTH *
227 				GFX_MONO_SPINCTRL_STRING_SPINNER_WIDTH);
228 
229 		/* Delete previous spinner data */
230 		gfx_mono_draw_filled_rect(offset, spinner->y,
231 				GFX_MONO_LCD_WIDTH - offset -
232 				GFX_MONO_SPINCTRL_SPIN_INDICATOR_WIDTH,
233 				SYSFONT_HEIGHT,
234 				GFX_PIXEL_CLR);
235 
236 		/* Draw string data */
237 		gfx_mono_draw_progmem_string(
238 				(char PROGMEM_PTR_T)spinner->strings.data[index], offset,
239 				spinner->y, &sysfont);
240 	}
241 }
242 
243 /**
244  * \brief Initialize a spinner.
245  *
246  * This function initializes a spinner to either
247  * \ref gfx_mono_spinctrl_type_t "SPINTYPE_STRING" spinner that spins through
248  * the strings in a \ref PROGMEM_STRING_T or a
249  * \ref gfx_mono_spinctrl_type_t "SPINTYPE_INTEGER" spinner that spins through
250  * integers.
251  * If the spinner type is SPINTYPE_INTEGER, it will spin from lower_limit to
252  * upper_limit.
253  * If the spinner type is SPINTYPE_STRING, a pointer to a PROGMEM_STRING_T must
254  * be provided, and the spinner will spin through the strings starting at
255  * index lower_limit ending at index upper_limit.
256  * The y parameter specifies where to place the spinner on the screen, but is
257  * overwritten if the spinner is put in a spincollection.
258  *
259  * \param[out] spinner      pointer to initialized gfx_mono_spinctrl struct
260  * \param[in] datatype     typer of spinner, integer or string
261  * \param[in] title        title of spinner
262  * \param[in] data         pointer to progmem string array if datatype is string.
263  *                     NULL if datatype is integer.
264  * \param[in] lower_limit  lower limit and start value of spinner's data
265  * \param[in] upper_limit  upper limit of spinner's data
266  * \param[in] y            y position of spinner
267  */
gfx_mono_spinctrl_init(struct gfx_mono_spinctrl * spinner,gfx_mono_spinctrl_type_t datatype,PROGMEM_STRING_T title,PROGMEM_STRING_T * data,int16_t lower_limit,int16_t upper_limit,gfx_coord_t y)268 void gfx_mono_spinctrl_init(struct gfx_mono_spinctrl *spinner,
269 		gfx_mono_spinctrl_type_t datatype, PROGMEM_STRING_T title,
270 		PROGMEM_STRING_T *data, int16_t lower_limit,
271 		int16_t upper_limit,
272 		gfx_coord_t y)
273 {
274 	/* Initialization of spinner parameters */
275 	spinner->title = title;
276 	spinner->datatype = datatype;
277 	spinner->lower_limit = lower_limit;
278 	spinner->upper_limit = upper_limit;
279 	spinner->y = y;
280 	spinner->in_focus = false;
281 	spinner->last_saved_value = spinner->lower_limit;
282 
283 	if (datatype == SPINTYPE_STRING) {
284 		spinner->strings.data = data;
285 		spinner->strings.index = lower_limit;
286 	} else {
287 		spinner->integer_data = lower_limit;
288 	}
289 }
290 
291 /**
292  * \brief Initialize a spincollection.
293  *
294  * This function initializes a spincollection to which spinners can be added.
295  *
296  * \param[out] collection pointer to gfx_mono_spinctrl_spincollection to Initialize
297  */
gfx_mono_spinctrl_spincollection_init(struct gfx_mono_spinctrl_spincollection * collection)298 void gfx_mono_spinctrl_spincollection_init(struct
299 		gfx_mono_spinctrl_spincollection *collection)
300 {
301 	collection->active_spinner = false;
302 	collection->current_selection = 0;
303 	collection->number_of_spinners = 0;
304 	collection->init = true;
305 }
306 
307 /**
308  * \brief Add spinner to spincollection
309  *
310  * This function adds an initialized spinner to a spincollection and positions
311  * it below any other spinners in the spincollection on the screen. The
312  * spinners in the spincollection are linked together and number of spinners is
313  * updated.
314  * It is not possible to add more spinners than
315  * \ref GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION "maximum number of
316  * spinners in a spincollection".
317  *
318  * \param[in] spinner   pointer to initialized gfx_mono_spinctrl struct to add
319  *                      to collection
320  * \param[in] spinners  pointer to initialized gfx_mono_spinctrl_spincollection
321  *                      struct
322  *
323  */
gfx_mono_spinctrl_spincollection_add_spinner(struct gfx_mono_spinctrl * spinner,struct gfx_mono_spinctrl_spincollection * spinners)324 void gfx_mono_spinctrl_spincollection_add_spinner(struct
325 		gfx_mono_spinctrl *spinner,
326 		struct gfx_mono_spinctrl_spincollection *spinners)
327 {
328 	uint8_t i;
329 	struct gfx_mono_spinctrl *lastspinner;
330 
331 	/* Do not add more spinner elements than maximum number of spinners */
332 	if (spinners->number_of_spinners >=
333 			GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION) {
334 		return;
335 	}
336 
337 	/* Place new spinner below previous spinners on screen */
338 	spinner->y = (SYSFONT_HEIGHT + 1) * spinners->number_of_spinners;
339 
340 	/* Add pointer to the spinner in spincollection if empty */
341 	if (spinners->number_of_spinners == 0) {
342 		spinners->collection = spinner;
343 	} else {
344 		lastspinner = spinners->collection;
345 		for (i = 1; i < spinners->number_of_spinners; i++) {
346 			lastspinner = lastspinner->next;
347 		}
348 		/* Link the new spinner to the current last spinner in the
349 		 * collection */
350 		lastspinner->next = spinner;
351 		/* Link the current last spinner as previous spinner for new
352 		 * spinner */
353 		spinner->prev = lastspinner;
354 	}
355 
356 	/* Set added spinner as last spinner in collection */
357 	spinners->collection_last = spinner;
358 	/* Update number of spinners in collection */
359 	spinners->number_of_spinners++;
360 }
361 
362 /**
363  * \brief Show spincollection
364  *
365  * This function draws all the spinners in a spincollection to the screen,
366  * together with an OK button at the bottom. It also draws an indicator arrow
367  * in front of the top spinner.
368  *
369  * \param[in] spinners pointer to initialized spincollection to display
370  */
gfx_mono_spinctrl_spincollection_show(struct gfx_mono_spinctrl_spincollection * spinners)371 void gfx_mono_spinctrl_spincollection_show(struct
372 		gfx_mono_spinctrl_spincollection *spinners)
373 {
374 	uint8_t i;
375 	struct gfx_mono_spinctrl *iterator;
376 
377 	/* Clear screen */
378 	gfx_mono_draw_filled_rect(0, 0, GFX_MONO_LCD_WIDTH, GFX_MONO_LCD_HEIGHT,
379 			GFX_PIXEL_CLR);
380 
381 	/* Make sure there are spinners in the collection */
382 	if (spinners->number_of_spinners == 0) {
383 		return;
384 	}
385 
386 	/* Draw spinners on screen */
387 	iterator = spinners->collection;
388 	for (i = 0; i < spinners->number_of_spinners; i++) {
389 		gfx_mono_spinctrl_draw(iterator, true);
390 		iterator = iterator->next;
391 	}
392 	/* Draw OK button at bottom of screen */
393 	gfx_mono_spinctrl_draw_button(true, false);
394 	/* Draw indicator arrow in front of first spinner */
395 	gfx_mono_spinctrl_draw_indicator(spinners->collection, true);
396 }
397 
398 /**
399  * \brief Step up spinner data
400  *
401  * This function steps up the data, making sure it does not go
402  * beyond the upper limit. Wraps around if it does.
403  *
404  * \param[in] spinner pointer to initialized spinner.
405  */
gfx_mono_spinctrl_step_up(struct gfx_mono_spinctrl * spinner)406 static void gfx_mono_spinctrl_step_up(struct gfx_mono_spinctrl *spinner)
407 {
408 	/* Check if spinner type is integer or string, increment integer data or
409 	 * move to next string index.
410 	 */
411 	if (spinner->datatype == SPINTYPE_INTEGER) {
412 		if (spinner->integer_data < spinner->upper_limit) {
413 			spinner->integer_data++;
414 		} else {
415 			spinner->integer_data = spinner->lower_limit;
416 		}
417 	} else if (spinner->datatype == SPINTYPE_STRING) {
418 		if (spinner->strings.index < spinner->upper_limit) {
419 			spinner->strings.index++;
420 		} else {
421 			(spinner->strings.index) = spinner->lower_limit;
422 		}
423 	}
424 }
425 
426 /**
427  * \brief Step down spinner data
428  *
429  * * This function steps down the data, making sure it does not go
430  * below the lower limit. Wraps around if it does.
431  *
432  * \param[in] spinner pointer to initialized spinner.
433  */
gfx_mono_spinctrl_step_down(struct gfx_mono_spinctrl * spinner)434 static void gfx_mono_spinctrl_step_down(struct gfx_mono_spinctrl *spinner)
435 {
436 	/* Check if spinner type is integer or string, increment integer data,
437 	 * move to next string index.
438 	 */
439 	if (spinner->datatype == SPINTYPE_INTEGER) {
440 		if (spinner->integer_data > spinner->lower_limit) {
441 			spinner->integer_data--;
442 		} else {
443 			spinner->integer_data = spinner->upper_limit;
444 		}
445 	} else if (spinner->datatype == SPINTYPE_STRING) {
446 		if (spinner->strings.index > (spinner->lower_limit)) {
447 			spinner->strings.index--;
448 		} else {
449 			spinner->strings.index = spinner->upper_limit;
450 		}
451 	}
452 }
453 
454 /**
455  * \brief Update single spinner depending on input.
456  *
457  * \param[in] spinner pointer to initialized spinner.
458  * \param[in] keycode  keycode to process
459  *
460  * \retval selected            selected spinner value
461  * \retval GFX_MONO_SPINCTRL_EVENT_IDLE spinner spinning
462  * \retval GFX_MONO_SPINCTRL_EVENT_BACK spinner deselected
463  */
gfx_mono_spinctrl_process_key(struct gfx_mono_spinctrl * spinner,uint8_t keycode)464 int16_t gfx_mono_spinctrl_process_key(struct gfx_mono_spinctrl *spinner,
465 		uint8_t keycode)
466 {
467 	switch (keycode) {
468 	case GFX_MONO_SPINCTRL_KEYCODE_DOWN:
469 		if (spinner->in_focus) {
470 			gfx_mono_spinctrl_step_down(spinner);
471 			/* Update spinner on display */
472 			gfx_mono_spinctrl_draw(spinner, false);
473 		}
474 
475 		/* Nothing selected yet */
476 		return GFX_MONO_SPINCTRL_EVENT_IDLE;
477 
478 	case GFX_MONO_SPINCTRL_KEYCODE_UP:
479 		if (spinner->in_focus) {
480 			gfx_mono_spinctrl_step_up(spinner);
481 			/* Update spinner on display */
482 			gfx_mono_spinctrl_draw(spinner, false);
483 		}
484 
485 		/* Nothing selected yet */
486 		return GFX_MONO_SPINCTRL_EVENT_IDLE;
487 
488 	case GFX_MONO_SPINCTRL_KEYCODE_ENTER:
489 		if (spinner->in_focus) {
490 			if (spinner->datatype == SPINTYPE_INTEGER) {
491 				spinner->in_focus = false;
492 				gfx_mono_spinctrl_draw(spinner, false);
493 				/* Store saved value in case of aborting spinner
494 				 * later */
495 				spinner->last_saved_value
496 					= spinner->integer_data;
497 				/* Got what we want. Return selection. */
498 				return spinner->integer_data;
499 			} else if (spinner->datatype == SPINTYPE_STRING) {
500 				spinner->in_focus = false;
501 				gfx_mono_spinctrl_draw(spinner, false);
502 				/* Store saved value in case of aborting spinner
503 				 * later */
504 				spinner->last_saved_value
505 					= spinner->strings.index;
506 				/* Got what we want. Return selection. */
507 				return spinner->strings.index;
508 			}
509 		} else {
510 			/* Spinner selected */
511 			spinner->in_focus = true;
512 			gfx_mono_spinctrl_draw(spinner, false);
513 			return GFX_MONO_SPINCTRL_EVENT_IDLE;
514 		}
515 
516 	case GFX_MONO_SPINCTRL_KEYCODE_BACK:
517 		/* User pressed "back" key, */
518 		spinner->in_focus = false;
519 		/* Spinner choice aborted, show last saved value instead */
520 		if (spinner->datatype == SPINTYPE_INTEGER) {
521 			spinner->integer_data = spinner->last_saved_value;
522 		} else if (spinner->datatype == SPINTYPE_STRING) {
523 			spinner->strings.index = spinner->last_saved_value;
524 		}
525 
526 		gfx_mono_spinctrl_draw(spinner, false);
527 		return GFX_MONO_SPINCTRL_EVENT_BACK;
528 
529 	default:
530 		/* Unknown key event */
531 		return GFX_MONO_SPINCTRL_EVENT_IDLE;
532 	}
533 }
534 
535 /**
536  * \brief Update spincollection on screen depending on input
537  *
538  * This function returns \ref GFX_MONO_SPINCTRL_EVENT_FINISH if user has
539  * pressed the OK button. The spinner choices can then be extracted from the
540  * results array. If a spinner is of type SPINTYPE_STRING, the index of the
541  * progmem string will be stored in the results array, else the selected
542  * integer value will be stored.
543  * The choice from the first spinner added to the spincollection will be stored
544  * at index 0 in the results array, the second at index 1 and so on.
545  * If user has pressed the back button, \ref GFX_MONO_SPINCTRL_EVENT_BACK is
546  * returned, signalling that the application should be cancelled.
547  *
548  * \param[in] spinners       pointer to initialized
549  *                       gfx_mono_spinctrl_spincollection
550  * \param[in] keycode        keycode to process
551  * \param[in] results        array to store results from the spinners, must be of
552  *                       same length as number of spinners
553  *
554  * \retval GFX_MONO_SPINCTRL_EVENT_FINISH  user pressed ok button
555  * \retval GFX_MONO_SPINCTRL_EVENT_BACK    user cancelled
556  * \retval GFX_MONO_SPINCTRL_EVENT_IDLE    user is navigating in spincollection
557  */
gfx_mono_spinctrl_spincollection_process_key(struct gfx_mono_spinctrl_spincollection * spinners,uint8_t keycode,int16_t results[])558 int16_t gfx_mono_spinctrl_spincollection_process_key(struct
559 		gfx_mono_spinctrl_spincollection *spinners, uint8_t keycode,
560 		int16_t results[])
561 {
562 	uint8_t i;
563 	struct gfx_mono_spinctrl *iterator;
564 
565 	/* Make sure there are spinners in the collection, if not, cancel */
566 	if (spinners->number_of_spinners == 0) {
567 		return GFX_MONO_SPINCTRL_EVENT_BACK;
568 	}
569 
570 	/* Store initial values in results array first time function is run */
571 	if (spinners->init) {
572 		iterator = spinners->collection;
573 		for (i = 0; i < spinners->number_of_spinners; i++) {
574 			if (iterator->datatype == SPINTYPE_INTEGER) {
575 				results[i] = iterator->integer_data;
576 			} else {
577 				results[i] = iterator->strings.index;
578 			}
579 
580 			iterator = iterator->next;
581 		}
582 		spinners->init = false;
583 	}
584 
585 	/* Find current spinner selection */
586 	iterator = spinners->collection;
587 	if (spinners->current_selection != GFX_MONO_SPINCTRL_BUTTON) {
588 		for (i = 0; i < spinners->current_selection; i++) {
589 			iterator = iterator->next;
590 		}
591 	}
592 
593 	if (spinners->active_spinner) {
594 		/* Process chosen spinner */
595 		spinners->selection = gfx_mono_spinctrl_process_key(iterator,
596 				keycode);
597 		if (spinners->selection == GFX_MONO_SPINCTRL_EVENT_BACK) {
598 			/* User has exited spinner without saving the result */
599 			spinners->active_spinner = false;
600 		} else if (spinners->selection !=
601 				GFX_MONO_SPINCTRL_EVENT_IDLE) {
602 			/* Value selected, store in array */
603 			results[spinners->current_selection]
604 				= spinners->selection;
605 			/* Step out of spinner and into spincollection */
606 			spinners->active_spinner = false;
607 		}
608 
609 		return GFX_MONO_SPINCTRL_EVENT_IDLE;
610 	} else {
611 		switch (keycode) {
612 		case GFX_MONO_SPINCTRL_KEYCODE_DOWN:
613 			if (spinners->current_selection ==
614 					GFX_MONO_SPINCTRL_BUTTON) {
615 				spinners->current_selection = 0;
616 				/* Delete indicator arrow in front of button */
617 				gfx_mono_spinctrl_draw_button(true, false);
618 				/* Draw indicator arrow in front of first
619 				 * spinner */
620 				gfx_mono_spinctrl_draw_indicator(iterator,
621 						true);
622 			} else if (spinners->current_selection <
623 					spinners->number_of_spinners - 1) {
624 				/* Delete indicator arrow */
625 				gfx_mono_spinctrl_draw_indicator(iterator,
626 						false);
627 				spinners->current_selection++;
628 				/* Draw indicator arrow in front of new spinner */
629 				gfx_mono_spinctrl_draw_indicator(iterator->next,
630 						true);
631 			} else {
632 				/* Delete indicator arrow */
633 				gfx_mono_spinctrl_draw_indicator(iterator,
634 						false);
635 				spinners->current_selection
636 					= GFX_MONO_SPINCTRL_BUTTON;
637 				/* Draw indicator arrow in front of button */
638 				gfx_mono_spinctrl_draw_button(true, true);
639 			}
640 
641 			return GFX_MONO_SPINCTRL_EVENT_IDLE;
642 
643 		case GFX_MONO_SPINCTRL_KEYCODE_UP:
644 			if (spinners->current_selection ==
645 					GFX_MONO_SPINCTRL_BUTTON) {
646 				/* Delete indicator arrow in front of button */
647 				gfx_mono_spinctrl_draw_button(true, false);
648 				spinners->current_selection
649 					= spinners->number_of_spinners - 1;
650 				/* Draw indicator arrow in front of new spinner */
651 				gfx_mono_spinctrl_draw_indicator(
652 						spinners->collection_last,
653 						true);
654 			} else if (spinners->current_selection > 0) {
655 				/* Delete indicator arrow */
656 				gfx_mono_spinctrl_draw_indicator(iterator,
657 						false);
658 				spinners->current_selection--;
659 				/* Draw indicator arrow in front of new spinner */
660 				gfx_mono_spinctrl_draw_indicator(iterator->prev,
661 						true);
662 			} else {
663 				/* Delete indicator arrow */
664 				gfx_mono_spinctrl_draw_indicator(iterator,
665 						false);
666 				spinners->current_selection
667 					= GFX_MONO_SPINCTRL_BUTTON;
668 				/* Draw indicator arrow in front of button */
669 				gfx_mono_spinctrl_draw_button(true, true);
670 			}
671 
672 			return GFX_MONO_SPINCTRL_EVENT_IDLE;
673 
674 		case GFX_MONO_SPINCTRL_KEYCODE_ENTER:
675 			if (spinners->current_selection ==
676 					GFX_MONO_SPINCTRL_BUTTON) {
677 				/* Finished with all selections, return */
678 				return GFX_MONO_SPINCTRL_EVENT_FINISH;
679 			} else {
680 				/* Spinner selected, send next keycode directly
681 				 * to spinner */
682 				gfx_mono_spinctrl_process_key(iterator,
683 						keycode);
684 				spinners->active_spinner = true;
685 				return GFX_MONO_SPINCTRL_EVENT_IDLE;
686 			}
687 
688 		case GFX_MONO_SPINCTRL_KEYCODE_BACK:
689 			/* User pressed "back" key, */
690 			return GFX_MONO_SPINCTRL_EVENT_BACK;
691 
692 		default:
693 			/* Unknown key event */
694 			return GFX_MONO_SPINCTRL_EVENT_IDLE;
695 		}
696 	}
697 }
698