lists: Support '\t' in list item text to indent the line containing it.
[maemo-rb.git] / apps / gui / bitmap / list.c
blob45e6c03dcfe2d6b5c41898df490f3d221375dc58
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 by Jonathan Gordon
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 /* This file contains the code to draw the list widget on BITMAP LCDs. */
24 #include "config.h"
25 #include "system.h"
26 #include "lcd.h"
27 #include "font.h"
28 #include "button.h"
29 #include "string.h"
30 #include "settings.h"
31 #include "kernel.h"
32 #include "file.h"
34 #include "action.h"
35 #include "screen_access.h"
36 #include "list.h"
37 #include "scrollbar.h"
38 #include "lang.h"
39 #include "sound.h"
40 #include "misc.h"
41 #include "viewport.h"
42 #include "statusbar-skinned.h"
43 #include "debug.h"
45 #define ICON_PADDING 1
47 /* these are static to make scrolling work */
48 static struct viewport list_text[NB_SCREENS], title_text[NB_SCREENS];
50 #ifdef HAVE_TOUCHSCREEN
51 static int y_offset;
52 static bool hide_selection;
53 #endif
55 int gui_list_get_item_offset(struct gui_synclist * gui_list, int item_width,
56 int text_pos, struct screen * display,
57 struct viewport *vp);
58 bool list_display_title(struct gui_synclist *list, enum screen_type screen);
60 void gui_synclist_scroll_stop(struct gui_synclist *lists)
62 FOR_NB_SCREENS(i)
64 screens[i].scroll_stop(&list_text[i]);
65 screens[i].scroll_stop(&title_text[i]);
66 screens[i].scroll_stop(lists->parent[i]);
70 /* Draw the list...
71 internal screen layout:
72 -----------------
73 |TI| title | TI is title icon
74 -----------------
75 | | | |
76 |S|I| | S - scrollbar
77 | | | items | I - icons
78 | | | |
79 ------------------
81 Note: This image is flipped horizontally when the language is a
82 right-to-left one (Hebrew, Arabic)
85 static int list_icon_width(enum screen_type screen)
87 return get_icon_width(screen) + ICON_PADDING * 2;
90 static int list_icon_height(enum screen_type screen)
92 return get_icon_height(screen);
95 static bool draw_title(struct screen *display, struct gui_synclist *list)
97 const int screen = display->screen_type;
98 int style = STYLE_DEFAULT;
99 struct viewport *title_text_vp = &title_text[screen];
101 if (sb_set_title_text(list->title, list->title_icon, screen))
102 return false; /* the sbs is handling the title */
103 display->scroll_stop(title_text_vp);
104 if (!list_display_title(list, screen))
105 return false;
106 *title_text_vp = *(list->parent[screen]);
107 title_text_vp->height = title_text_vp->line_height;
109 if (list->title_icon != Icon_NOICON && global_settings.show_icons)
111 struct viewport title_icon = *title_text_vp;
113 title_icon.width = list_icon_width(screen);
114 title_icon.y += (title_icon.height - list_icon_height(screen)) / 2;
115 title_icon.height = list_icon_height(screen);
116 if (VP_IS_RTL(&title_icon))
118 title_icon.x += title_text_vp->width - title_icon.width;
120 else
122 title_text_vp->x += title_icon.width;
124 title_text_vp->width -= title_icon.width;
126 display->set_viewport(&title_icon);
127 screen_put_iconxy(display, 0, 0, list->title_icon);
129 #ifdef HAVE_LCD_COLOR
130 if (list->title_color >= 0)
132 style |= (STYLE_COLORED|list->title_color);
134 #endif
135 display->set_viewport(title_text_vp);
136 display->puts_scroll_style(0, 0, list->title, style);
137 return true;
140 void list_draw(struct screen *display, struct gui_synclist *list)
142 struct viewport list_icons;
143 int start, end, line_height, style, item_offset, i;
144 const int screen = display->screen_type;
145 const int list_start_item = list->start_item[screen];
146 const int icon_width = list_icon_width(screen);
147 const bool scrollbar_in_left = (global_settings.scrollbar == SCROLLBAR_LEFT);
148 const bool show_cursor = !global_settings.cursor_style &&
149 list->show_selection_marker;
150 struct viewport *parent = (list->parent[screen]);
151 #ifdef HAVE_LCD_COLOR
152 unsigned char cur_line = 0;
153 #endif
154 int icon_yoffset = 0; /* to center the icon */
155 bool show_title;
156 struct viewport *list_text_vp = &list_text[screen];
157 int indent = 0;
159 line_height = parent->line_height;
160 display->set_viewport(parent);
161 display->clear_viewport();
162 display->scroll_stop(list_text_vp);
163 *list_text_vp = *parent;
164 list_text_vp->line_height = line_height;
165 if ((show_title = draw_title(display, list)))
167 int title_height = title_text[screen].height;
168 list_text_vp->y += title_height;
169 list_text_vp->height -= title_height;
172 const int nb_lines = viewport_get_nb_lines(list_text_vp);
174 start = list_start_item;
175 end = start + nb_lines;
177 #ifdef HAVE_TOUCHSCREEN
178 if (list->selected_item == 0 || (list->nb_items < nb_lines))
179 y_offset = 0; /* reset in case it's a new list */
181 int draw_offset = y_offset;
182 /* draw some extra items to not have empty lines at the top and bottom */
183 if (y_offset > 0)
185 /* make it negative for more consistent apparence when switching
186 * directions */
187 draw_offset -= line_height;
188 if (start > 0)
189 start--;
191 else if (y_offset < 0)
192 end++;
193 #else
194 #define draw_offset 0
195 #endif
197 /* draw the scrollbar if its needed */
198 if (global_settings.scrollbar && nb_lines < list->nb_items)
200 struct viewport vp = *list_text_vp;
201 vp.width = SCROLLBAR_WIDTH;
202 vp.height = line_height * nb_lines;
203 vp.x = parent->x;
204 list_text_vp->width -= SCROLLBAR_WIDTH;
205 if (scrollbar_in_left)
206 list_text_vp->x += SCROLLBAR_WIDTH;
207 else
208 vp.x += list_text_vp->width;
209 display->set_viewport(&vp);
210 gui_scrollbar_draw(display,
211 (scrollbar_in_left? 0: 1), 0, SCROLLBAR_WIDTH-1, vp.height,
212 list->nb_items, list_start_item, list_start_item + nb_lines,
213 VERTICAL);
215 else if (show_title)
217 /* shift everything a bit in relation to the title... */
218 if (!VP_IS_RTL(list_text_vp) && scrollbar_in_left)
220 list_text_vp->width -= SCROLLBAR_WIDTH;
221 list_text_vp->x += SCROLLBAR_WIDTH;
223 else if (VP_IS_RTL(list_text_vp) && !scrollbar_in_left)
225 list_text_vp->width -= SCROLLBAR_WIDTH;
229 /* setup icon placement */
230 list_icons = *list_text_vp;
231 int icon_count = (list->callback_get_item_icon != NULL) ? 1 : 0;
232 if (show_cursor)
233 icon_count++;
234 if (icon_count)
236 list_icons.width = icon_width * icon_count;
237 list_text_vp->width -= list_icons.width + ICON_PADDING;
238 if (VP_IS_RTL(&list_icons))
239 list_icons.x += list_text_vp->width + ICON_PADDING;
240 else
241 list_text_vp->x += list_icons.width + ICON_PADDING;
242 icon_yoffset = (line_height - list_icon_height(screen)) / 2;
245 for (i=start; i<end && i<list->nb_items; i++)
247 /* do the text */
248 unsigned const char *s;
249 char entry_buffer[MAX_PATH];
250 unsigned char *entry_name;
251 int text_pos = 0;
252 int line = i - start;
253 indent = 0;
254 s = list->callback_get_item_name(i, list->data, entry_buffer,
255 sizeof(entry_buffer));
256 entry_name = P2STR(s);
258 while (*entry_name == '\t')
260 indent++;
261 entry_name++;
263 if (indent)
265 if (icon_width)
266 indent *= icon_width;
267 else
268 indent *= display->getcharwidth();
270 list_icons.x += indent;
271 list_text_vp->x += indent;
272 list_text_vp->width -= indent;
275 display->set_viewport(list_text_vp);
276 style = STYLE_DEFAULT;
277 /* position the string at the correct offset place */
278 int item_width,h;
279 display->getstringsize(entry_name, &item_width, &h);
280 item_offset = gui_list_get_item_offset(list, item_width, text_pos,
281 display, list_text_vp);
283 #ifdef HAVE_LCD_COLOR
284 /* if the list has a color callback */
285 if (list->callback_get_item_color)
287 int color = list->callback_get_item_color(i, list->data);
288 /* if color selected */
289 if (color >= 0)
291 style |= STYLE_COLORED|color;
294 #endif
295 /* draw the selected line */
297 #ifdef HAVE_TOUCHSCREEN
298 /* don't draw it during scrolling */
299 !hide_selection &&
300 #endif
301 i >= list->selected_item
302 && i < list->selected_item + list->selected_size
303 && list->show_selection_marker)
304 {/* The selected item must be displayed scrolling */
305 if (global_settings.cursor_style == 1
306 #ifdef HAVE_REMOTE_LCD
307 /* the global_settings.cursor_style check is here to make
308 * sure if they want the cursor instead of bar it will work
310 || (display->depth < 16 && global_settings.cursor_style)
311 #endif
314 /* Display inverted-line-style */
315 style = STYLE_INVERT;
317 #ifdef HAVE_LCD_COLOR
318 else if (global_settings.cursor_style == 2)
320 /* Display colour line selector */
321 style = STYLE_COLORBAR;
323 else if (global_settings.cursor_style == 3)
325 /* Display gradient line selector */
326 style = STYLE_GRADIENT;
328 /* Make the lcd driver know how many lines the gradient should
329 cover and current line number */
330 /* number of selected lines */
331 style |= NUMLN_PACK(list->selected_size);
332 /* current line number, zero based */
333 style |= CURLN_PACK(cur_line);
334 cur_line++;
336 #endif
337 /* if the text is smaller than the viewport size */
338 if (item_offset> item_width - (list_text_vp->width - text_pos))
340 /* don't scroll */
341 display->puts_style_xyoffset(0, line, entry_name,
342 style, item_offset, draw_offset);
344 else
346 display->puts_scroll_style_xyoffset(0, line, entry_name,
347 style, item_offset, draw_offset);
350 else
352 if (list->scroll_all)
353 display->puts_scroll_style_xyoffset(0, line, entry_name,
354 style, item_offset, draw_offset);
355 else
356 display->puts_style_xyoffset(0, line, entry_name,
357 style, item_offset, draw_offset);
359 /* do the icon */
360 display->set_viewport(&list_icons);
361 if (list->callback_get_item_icon != NULL)
363 int xoff = show_cursor ? list_icon_width(screen) : 0;
364 screen_put_iconxy(display, xoff,
365 line*line_height + draw_offset + icon_yoffset,
366 list->callback_get_item_icon(i, list->data));
368 /* do the cursor */
369 if (show_cursor && i >= list->selected_item &&
370 i < list->selected_item + list->selected_size)
372 screen_put_iconxy(display, 0,
373 line*line_height + draw_offset + icon_yoffset,
374 Icon_Cursor);
376 if (indent)
378 list_icons.x -= indent;
379 list_text_vp->x -= indent;
380 list_text_vp->width += indent;
383 display->set_viewport(parent);
384 display->update_viewport();
385 display->set_viewport(NULL);
388 #if defined(HAVE_TOUCHSCREEN)
389 /* This needs to be fixed if we ever get more than 1 touchscreen on a target. */
391 /* difference in pixels between draws, above it means enough to start scrolling */
392 #define SCROLL_BEGIN_THRESHOLD 3
394 static enum {
395 SCROLL_NONE, /* no scrolling */
396 SCROLL_BAR, /* scroll by using the scrollbar */
397 SCROLL_SWIPE, /* scroll by wiping over the screen */
398 SCROLL_KINETIC, /* state after releasing swipe */
399 } scroll_mode;
401 static int scrollbar_scroll(struct gui_synclist * gui_list,
402 int y)
404 const int screen = screens[SCREEN_MAIN].screen_type;
405 const int nb_lines = viewport_get_nb_lines(&list_text[screen]);
407 if (nb_lines < gui_list->nb_items)
409 /* scrollbar scrolling is still line based */
410 y_offset = 0;
411 int scrollbar_size = nb_lines*gui_list->parent[screen]->line_height;
412 int actual_y = y - list_text[screen].y;
414 int new_selection = (actual_y * gui_list->nb_items)
415 / scrollbar_size;
417 int start_item = new_selection - nb_lines/2;
418 if(start_item < 0)
419 start_item = 0;
420 else if(start_item > gui_list->nb_items - nb_lines)
421 start_item = gui_list->nb_items - nb_lines;
423 gui_list->start_item[screen] = start_item;
425 return ACTION_REDRAW;
428 return ACTION_NONE;
431 /* kinetic scrolling, based on
433 * v = a*t + v0 and ds = v*dt
435 * In each (fixed interval) timeout, the list is advanced by ds, then
436 * the v is reduced by a.
437 * This way we get a linear and smooth deceleration of the scrolling
439 * As v is the difference of distance per time unit, v is passed (as
440 * pixels moved since the last call) to the scrolling function which takes
441 * care of the pixel accurate drawing
443 * v0 is dertermined by averaging the last 4 movements of the list
444 * (the pixel and time difference is used to compute each v)
446 * influenced by http://stechz.com/tag/kinetic/
447 * We take the easy and smooth first approach (until section "Drawbacks"),
448 * since its drawbacks don't apply for us since our timers seem to be
449 * relatively accurate
453 #define SIGN(a) ((a) < 0 ? -1 : 1)
454 /* these could possibly be configurable */
455 /* the lower the smoother */
456 #define RELOAD_INTERVAL (HZ/25)
457 /* the higher the earler the list stops */
458 #define DECELERATION (1000*RELOAD_INTERVAL/HZ)
460 /* this array holds data to compute the initial velocity v0 */
461 static struct kinetic_info {
462 int difference;
463 long ticks;
464 } kinetic_data[4];
465 static size_t cur_idx;
467 static struct cb_data {
468 struct gui_synclist *list; /* current list */
469 int velocity; /* in pixel/s */
470 } cb_data;
472 /* data member points to the above struct */
473 static struct timeout kinetic_tmo;
475 static bool is_kinetic_over(void)
477 return !cb_data.velocity && (scroll_mode == SCROLL_KINETIC);
481 * collect data about how fast the list is moved in order to compute
482 * the initial velocity from it later */
483 static void kinetic_stats_collect(const int difference)
485 static long last_tick;
486 /* collect velocity statistics */
487 kinetic_data[cur_idx].difference = difference;
488 kinetic_data[cur_idx].ticks = current_tick - last_tick;
490 last_tick = current_tick;
491 cur_idx += 1;
492 if (cur_idx >= ARRAYLEN(kinetic_data))
493 cur_idx = 0; /* rewind the index */
497 * resets the statistic */
498 static void kinetic_stats_reset(void)
500 memset(kinetic_data, 0, sizeof(kinetic_data));
501 cur_idx = 0;
504 /* cancels all currently active kinetic scrolling */
505 static void kinetic_force_stop(void)
507 timeout_cancel(&kinetic_tmo);
508 kinetic_stats_reset();
511 /* helper for gui/list.c to cancel scrolling if a normal button event comes
512 * through dpad or keyboard or whatever */
513 void _gui_synclist_stop_kinetic_scrolling(void)
515 y_offset = 0;
516 if (scroll_mode == SCROLL_KINETIC)
517 kinetic_force_stop();
518 scroll_mode = SCROLL_NONE;
519 hide_selection = false;
522 * returns false if scrolling should be stopped entirely
524 * otherwise it returns true even if it didn't actually scroll,
525 * but scrolling mode shouldn't be changed
529 static int scroll_begin_threshold;
530 static int threshold_accumulation;
531 static bool swipe_scroll(struct gui_synclist * gui_list, int difference)
533 /* fixme */
534 const enum screen_type screen = screens[SCREEN_MAIN].screen_type;
535 const int nb_lines = viewport_get_nb_lines(&list_text[screen]);
536 const int line_height = gui_list->parent[0]->line_height;
538 if (UNLIKELY(scroll_begin_threshold == 0))
539 scroll_begin_threshold = touchscreen_get_scroll_threshold();
541 /* make selecting items easier */
542 threshold_accumulation += abs(difference);
543 if (threshold_accumulation < scroll_begin_threshold && scroll_mode == SCROLL_NONE)
544 return false;
546 threshold_accumulation = 0;
548 /* does the list even scroll? if no, return but still show
549 * the caller that we would scroll */
550 if (nb_lines >= gui_list->nb_items)
551 return true;
553 const int old_start = gui_list->start_item[screen];
554 int new_start_item = -1;
555 int line_diff = 0;
557 /* don't scroll at the edges of the list */
558 if ((old_start == 0 && difference > 0)
559 || (old_start == (gui_list->nb_items - nb_lines) && difference < 0))
561 y_offset = 0;
562 gui_list->start_item[screen] = old_start;
563 return scroll_mode != SCROLL_KINETIC; /* stop kinetic at the edges */
566 /* add up y_offset over time and translate to lines
567 * if scrolled enough */
568 y_offset += difference;
569 if (abs(y_offset) > line_height)
571 line_diff = y_offset/line_height;
572 y_offset -= line_diff * line_height;
575 if(line_diff != 0)
577 int selection_offset = gui_list->selected_item - old_start;
578 new_start_item = old_start - line_diff;
579 /* check if new_start_item is bigger than list item count */
580 if(new_start_item > gui_list->nb_items - nb_lines)
581 new_start_item = gui_list->nb_items - nb_lines;
582 /* set new_start_item to 0 if it's negative */
583 if(new_start_item < 0)
584 new_start_item = 0;
586 gui_list->start_item[screen] = new_start_item;
587 /* keep selected item in sync */
588 gui_list->selected_item = new_start_item + selection_offset;
591 return true;
594 static int kinetic_callback(struct timeout *tmo)
596 /* cancel if screen was pressed */
597 if (scroll_mode != SCROLL_KINETIC)
598 return 0;
600 struct cb_data *data = (struct cb_data*)tmo->data;
601 /* ds = v*dt */
602 int pixel_diff = data->velocity * RELOAD_INTERVAL / HZ;
603 /* remember signedness to detect stopping */
604 int old_sign = SIGN(data->velocity);
605 /* advance the list */
606 if (!swipe_scroll(data->list, pixel_diff))
608 /* nothing to scroll? */
609 data->velocity = 0;
611 else
613 /* decelerate by a fixed amount
614 * decrementing v0 over time by the deceleration is
615 * equivalent to computing v = a*t + v0 */
616 data->velocity -= SIGN(data->velocity)*DECELERATION;
617 if (SIGN(data->velocity) != old_sign)
618 data->velocity = 0;
621 /* let get_action() timeout, which loads to a
622 * gui_synclist_draw() call from the main thread */
623 queue_post(&button_queue, BUTTON_REDRAW, 0);
624 /* stop if the velocity hit or crossed zero */
625 if (!data->velocity)
627 kinetic_stats_reset();
628 return 0;
630 return RELOAD_INTERVAL; /* cancel or reload */
634 * computes the initial velocity v0 and sets up the timer */
635 static bool kinetic_setup_scroll(struct gui_synclist *list)
637 /* compute initial velocity */
638 int i, _i, v0, len = ARRAYLEN(kinetic_data);
639 for(i = 0, _i = 0, v0 = 0; i < len; i++)
640 { /* in pixel/s */
641 if (kinetic_data[i].ticks > 0)
643 v0 += kinetic_data[i].difference*HZ/kinetic_data[i].ticks;
644 _i++;
647 if (_i > 0)
648 v0 /= _i;
649 else
650 v0 = 0;
652 if (v0 != 0)
654 cb_data.list = list;
655 cb_data.velocity = v0;
656 timeout_register(&kinetic_tmo, kinetic_callback, RELOAD_INTERVAL, (intptr_t)&cb_data);
657 return true;
659 return false;
662 #define OUTSIDE 0
663 #define TITLE_TEXT (1<<0)
664 #define TITLE_ICON (1<<1)
665 #define SCROLLBAR (1<<2)
666 #define LIST_TEXT (1<<3)
667 #define LIST_ICON (1<<4)
669 #define TITLE (TITLE_TEXT|TITLE_ICON)
670 #define LIST (LIST_TEXT|LIST_ICON)
672 static int get_click_location(struct gui_synclist *list, int x, int y)
674 int screen = SCREEN_MAIN;
675 struct viewport *parent, *title, *text;
676 int retval = OUTSIDE;
678 parent = list->parent[screen];
679 if (viewport_point_within_vp(parent, x, y))
681 /* see if the title was clicked */
682 title = &title_text[screen];
683 if (viewport_point_within_vp(title, x, y))
684 retval = TITLE_TEXT;
685 /* check the icon too */
686 if (list->title_icon != Icon_NOICON && global_settings.show_icons)
688 int width = list_icon_width(screen);
689 struct viewport vp = *title;
690 if (VP_IS_RTL(&vp))
691 vp.x += vp.width;
692 else
693 vp.x -= width;
694 vp.width = width;
695 if (viewport_point_within_vp(&vp, x, y))
696 retval = TITLE_ICON;
698 /* check scrollbar. assume it's shown, if it isn't it will be handled
699 * later */
700 if (retval == OUTSIDE)
702 bool on_scrollbar_clicked;
703 int adj_x = x - parent->x;
704 switch (global_settings.scrollbar)
706 case SCROLLBAR_LEFT:
707 on_scrollbar_clicked = adj_x <= SCROLLBAR_WIDTH; break;
708 case SCROLLBAR_RIGHT:
709 on_scrollbar_clicked = adj_x > (title->x + title->width - SCROLLBAR_WIDTH); break;
710 default:
711 on_scrollbar_clicked = false; break;
713 if (on_scrollbar_clicked)
714 retval = SCROLLBAR;
716 if (retval == OUTSIDE)
718 text = &list_text[screen];
719 if (viewport_point_within_vp(text, x, y))
720 retval = LIST_TEXT;
721 else /* if all fails, it must be on the list icons */
722 retval = LIST_ICON;
725 return retval;
728 unsigned gui_synclist_do_touchscreen(struct gui_synclist * list)
730 enum screen_type screen;
731 struct viewport *parent;
732 short x, y;
733 int action, adj_x, adj_y, line, line_height, list_start_item;
734 bool recurse;
735 static int last_y = -1;
737 screen = SCREEN_MAIN;
738 parent = list->parent[screen];
739 line_height = list->parent[screen]->line_height;
740 list_start_item = list->start_item[screen];
741 /* start with getting the action code and finding the click location */
742 action = action_get_touchscreen_press(&x, &y);
743 adj_x = x - parent->x;
744 adj_y = y - parent->y;
747 /* some defaults before running the state machine */
748 recurse = false;
749 hide_selection = false;
751 switch (scroll_mode)
753 case SCROLL_NONE:
755 if (last_y == -1)
756 { /* first run. register adj_y and re-run (will then take the else case) */
757 last_y = adj_y;
758 recurse = true;
760 else
762 int click_loc = get_click_location(list, x, y);
763 line = 0; /* silence gcc 'used uninitialized' warning */
764 if (click_loc & LIST)
766 if(!skinlist_get_item(&screens[screen], list, adj_x, adj_y, &line))
768 /* selection needs to be corrected if items are only partially visible */
769 line = (adj_y - y_offset) / line_height;
770 if (list_display_title(list, screen))
771 line -= 1; /* adjust for the list title */
773 if (line >= list->nb_items)
774 return ACTION_NONE;
775 list->selected_item = list_start_item+line;
777 gui_synclist_speak_item(list);
779 if (action == BUTTON_TOUCHSCREEN)
781 /* if not scrolling, the user is trying to select */
782 int diff = adj_y - last_y;
783 if ((click_loc & LIST) && swipe_scroll(list, diff))
784 scroll_mode = SCROLL_SWIPE;
785 else if (click_loc & SCROLLBAR)
786 scroll_mode = SCROLL_BAR;
788 else if (action == BUTTON_REPEAT)
790 if (click_loc & LIST)
792 /* held a single line for a while, bring up the context menu */
793 gui_synclist_select_item(list, list_start_item + line);
794 /* don't sent context repeatedly */
795 action_wait_for_release();
796 last_y = -1;
797 return ACTION_STD_CONTEXT;
800 else if (action & BUTTON_REL)
802 last_y = -1;
803 if (click_loc & LIST)
804 { /* release on list item enters it */
805 gui_synclist_select_item(list, list_start_item + line);
806 return ACTION_STD_OK;
808 else if (click_loc & TITLE_TEXT)
809 { /* clicking the title goes one level up (cancel) */
810 return ACTION_STD_CANCEL;
812 else if (click_loc & TITLE_ICON)
813 { /* clicking the title icon goes back to the root */
814 return ACTION_STD_MENU;
818 break;
820 case SCROLL_SWIPE:
822 /* when swipe scrolling, we accept outside presses as well and
823 * grab the entire screen (i.e. click_loc does not matter) */
824 int diff = adj_y - last_y;
825 hide_selection = true;
826 kinetic_stats_collect(diff);
827 if (swipe_scroll(list, diff))
829 /* letting the pen go enters kinetic scrolling */
830 if ((action & BUTTON_REL))
832 if (kinetic_setup_scroll(list))
834 hide_selection = true;
835 scroll_mode = SCROLL_KINETIC;
837 else
838 scroll_mode = SCROLL_NONE;
841 else if (action & BUTTON_REL)
842 scroll_mode = SCROLL_NONE;
844 if (scroll_mode == SCROLL_NONE)
845 last_y = -1;
846 break;
848 case SCROLL_KINETIC:
850 /* during kinetic scrolling we need to handle cancellation.
851 * This state is actually only entered upon end of it as this
852 * function is not called during the animation. */
853 if (!is_kinetic_over())
854 { /* a) the user touched the screen (manual cancellation) */
855 kinetic_force_stop();
856 if (get_click_location(list, x, y) & SCROLLBAR)
857 scroll_mode = SCROLL_BAR;
858 else
859 scroll_mode = SCROLL_SWIPE;
861 else
862 { /* b) kinetic scrolling stopped on its own */
863 /* need to re-run this with SCROLL_NONE since otherwise
864 * the next touch is not detected correctly */
865 scroll_mode = SCROLL_NONE;
866 recurse = true;
868 break;
870 case SCROLL_BAR:
872 hide_selection = true;
873 /* similarly to swipe scroll, using the scrollbar grabs
874 * focus so the click location is irrelevant */
875 scrollbar_scroll(list, adj_y);
876 if (action & BUTTON_REL)
877 scroll_mode = SCROLL_NONE;
878 break;
882 /* register y position unless forcefully reset to 1- */
883 if (last_y >= 0)
884 last_y = adj_y;
886 return recurse ? gui_synclist_do_touchscreen(list) : ACTION_REDRAW;
889 #endif