blob: a82595fb37203daff1ccefe7681c449a96f1c7b2 [file] [log] [blame]
Jonathan Gordon0e5cec22008-03-05 09:58:30 +00001/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007 by Jonathan Gordon
11 *
Daniel Stenberg2acc0ac2008-06-28 18:10:04 +000012 * 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.
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000016 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22/* This file contains the code to draw the list widget on BITMAP LCDs. */
23
24#include "config.h"
Michael Sevakisc9bcbe22012-03-27 19:52:15 -040025#include "system.h"
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000026#include "lcd.h"
27#include "font.h"
28#include "button.h"
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000029#include "string.h"
30#include "settings.h"
31#include "kernel.h"
Nils Wallménius4f879762009-08-19 21:48:17 +000032#include "file.h"
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000033
34#include "action.h"
35#include "screen_access.h"
36#include "list.h"
37#include "scrollbar.h"
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000038#include "lang.h"
39#include "sound.h"
40#include "misc.h"
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000041#include "viewport.h"
Jonathan Gordon466e5d92010-02-26 03:45:41 +000042#include "statusbar-skinned.h"
Thomas Martitzac08e692010-09-23 00:02:32 +000043#include "debug.h"
Thomas Martitzeec89a92013-12-20 23:34:28 +010044#include "line.h"
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000045
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000046#define ICON_PADDING 1
Thomas Martitzeec89a92013-12-20 23:34:28 +010047#define ICON_PADDING_S "1"
Tomer Shalev58221fc2009-10-05 20:22:02 +000048
Jonathan Gordone385ee12008-12-31 05:59:26 +000049/* these are static to make scrolling work */
50static struct viewport list_text[NB_SCREENS], title_text[NB_SCREENS];
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000051
Thomas Martitzf060cd52010-09-23 00:37:33 +000052#ifdef HAVE_TOUCHSCREEN
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +000053static int y_offset;
Thomas Martitz2760d572012-06-15 13:00:20 +020054static bool hide_selection;
Thomas Martitzf060cd52010-09-23 00:37:33 +000055#endif
56
Thomas Martitzeec89a92013-12-20 23:34:28 +010057/* list-private helpers from the generic list.c (move to header?) */
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000058int gui_list_get_item_offset(struct gui_synclist * gui_list, int item_width,
59 int text_pos, struct screen * display,
60 struct viewport *vp);
Jonathan Gordone385ee12008-12-31 05:59:26 +000061bool list_display_title(struct gui_synclist *list, enum screen_type screen);
Thomas Martitzeec89a92013-12-20 23:34:28 +010062int list_get_nb_lines(struct gui_synclist *list, enum screen_type screen);
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000063
Teruaki Kawashima93f9e7c2010-02-11 10:41:06 +000064void gui_synclist_scroll_stop(struct gui_synclist *lists)
65{
Teruaki Kawashima93f9e7c2010-02-11 10:41:06 +000066 FOR_NB_SCREENS(i)
67 {
Thomas Martitz1c5d0b42013-04-03 16:33:23 +020068 screens[i].scroll_stop_viewport(&list_text[i]);
69 screens[i].scroll_stop_viewport(&title_text[i]);
70 screens[i].scroll_stop_viewport(lists->parent[i]);
Teruaki Kawashima93f9e7c2010-02-11 10:41:06 +000071 }
72}
73
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000074/* Draw the list...
75 internal screen layout:
76 -----------------
77 |TI| title | TI is title icon
78 -----------------
79 | | | |
80 |S|I| | S - scrollbar
81 | | | items | I - icons
82 | | | |
83 ------------------
Tomer Shalev6d805652009-10-05 11:43:38 +000084
85 Note: This image is flipped horizontally when the language is a
86 right-to-left one (Hebrew, Arabic)
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000087*/
Thomas Martitz688302a2012-04-08 13:15:39 +020088
89static int list_icon_width(enum screen_type screen)
90{
91 return get_icon_width(screen) + ICON_PADDING * 2;
92}
93
Jonathan Gordone385ee12008-12-31 05:59:26 +000094static bool draw_title(struct screen *display, struct gui_synclist *list)
Jonathan Gordon0e5cec22008-03-05 09:58:30 +000095{
Jonathan Gordone385ee12008-12-31 05:59:26 +000096 const int screen = display->screen_type;
Tomer Shalev2438d162009-10-05 19:59:16 +000097 struct viewport *title_text_vp = &title_text[screen];
Thomas Martitzeec89a92013-12-20 23:34:28 +010098 struct line_desc line = LINE_DESC_DEFINIT;
Tomer Shalev2438d162009-10-05 19:59:16 +000099
Jonathan Gordon466e5d92010-02-26 03:45:41 +0000100 if (sb_set_title_text(list->title, list->title_icon, screen))
101 return false; /* the sbs is handling the title */
Thomas Martitz1c5d0b42013-04-03 16:33:23 +0200102 display->scroll_stop_viewport(title_text_vp);
Jonathan Gordone385ee12008-12-31 05:59:26 +0000103 if (!list_display_title(list, screen))
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000104 return false;
Tomer Shalev2438d162009-10-05 19:59:16 +0000105 *title_text_vp = *(list->parent[screen]);
Thomas Martitzeec89a92013-12-20 23:34:28 +0100106 line.height = list->line_height[screen];
107 title_text_vp->height = line.height;
Thomas Martitz0dd66412009-01-28 18:06:58 +0000108
Thomas Martitz05a67d02013-05-06 07:20:40 +0200109#if LCD_DEPTH > 1
110 /* XXX: Do we want to support the separator on remote displays? */
111 if (display->screen_type == SCREEN_MAIN && global_settings.list_separator_height != 0)
112 line.separator_height = abs(global_settings.list_separator_height)
113 + (lcd_get_dpi() > 200 ? 2 : 1);
114#endif
115
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000116#ifdef HAVE_LCD_COLOR
117 if (list->title_color >= 0)
Thomas Martitzeec89a92013-12-20 23:34:28 +0100118 line.style |= (STYLE_COLORED|list->title_color);
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000119#endif
Thomas Martitzeec89a92013-12-20 23:34:28 +0100120 line.scroll = true;
121
Tomer Shalev2438d162009-10-05 19:59:16 +0000122 display->set_viewport(title_text_vp);
Thomas Martitzeec89a92013-12-20 23:34:28 +0100123
124 if (list->title_icon != Icon_NOICON && global_settings.show_icons)
125 put_line(display, 0, 0, &line, "$"ICON_PADDING_S"I$t",
126 list->title_icon, list->title);
127 else
128 put_line(display, 0, 0, &line, "$t", list->title);
129
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000130 return true;
131}
Maurus Cuelenaere07b27082009-08-22 21:46:02 +0000132
Jonathan Gordone385ee12008-12-31 05:59:26 +0000133void list_draw(struct screen *display, struct gui_synclist *list)
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000134{
Thomas Martitzeec89a92013-12-20 23:34:28 +0100135 int start, end, item_offset, i;
Jonathan Gordone385ee12008-12-31 05:59:26 +0000136 const int screen = display->screen_type;
Tomer Shalev2438d162009-10-05 19:59:16 +0000137 const int list_start_item = list->start_item[screen];
Bertrik Sikken92d28742010-01-03 13:14:50 +0000138 const bool scrollbar_in_left = (global_settings.scrollbar == SCROLLBAR_LEFT);
Thomas Martitz48bc7622013-01-18 18:02:47 +0100139 const bool scrollbar_in_right = (global_settings.scrollbar == SCROLLBAR_RIGHT);
Jonathan Gordone385ee12008-12-31 05:59:26 +0000140 const bool show_cursor = !global_settings.cursor_style &&
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000141 list->show_selection_marker;
Thomas Martitzeec89a92013-12-20 23:34:28 +0100142 const bool have_icons = global_settings.show_icons && list->callback_get_item_icon;
Jonathan Gordone385ee12008-12-31 05:59:26 +0000143 struct viewport *parent = (list->parent[screen]);
Thomas Martitzeec89a92013-12-20 23:34:28 +0100144 struct line_desc linedes = LINE_DESC_DEFINIT;
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000145 bool show_title;
Tomer Shalev2438d162009-10-05 19:59:16 +0000146 struct viewport *list_text_vp = &list_text[screen];
Jonathan Gordon08238172012-07-18 23:19:11 +0200147 int indent = 0;
Tomer Shalev2438d162009-10-05 19:59:16 +0000148
William Wilgus3237ae42020-10-07 02:01:35 -0400149 struct viewport * last_vp = display->set_viewport(parent);
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000150 display->clear_viewport();
Thomas Martitz1c5d0b42013-04-03 16:33:23 +0200151 display->scroll_stop_viewport(list_text_vp);
Tomer Shalev2438d162009-10-05 19:59:16 +0000152 *list_text_vp = *parent;
Jonathan Gordone385ee12008-12-31 05:59:26 +0000153 if ((show_title = draw_title(display, list)))
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000154 {
Thomas Martitz3b126342011-10-17 17:38:10 +0000155 int title_height = title_text[screen].height;
156 list_text_vp->y += title_height;
157 list_text_vp->height -= title_height;
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000158 }
Jonathan Gordone385ee12008-12-31 05:59:26 +0000159
Thomas Martitzeec89a92013-12-20 23:34:28 +0100160 const int nb_lines = list_get_nb_lines(list, screen);
161
162 linedes.height = list->line_height[screen];
163 linedes.nlines = list->selected_size;
Thomas Martitz05a67d02013-05-06 07:20:40 +0200164#if LCD_DEPTH > 1
165 /* XXX: Do we want to support the separator on remote displays? */
166 if (display->screen_type == SCREEN_MAIN)
167 linedes.separator_height = abs(global_settings.list_separator_height);
168#endif
Tomer Shalev2438d162009-10-05 19:59:16 +0000169 start = list_start_item;
Thomas Martitzb673ae22010-10-31 11:11:46 +0000170 end = start + nb_lines;
Tomer Shalev1acacc22009-10-05 18:11:41 +0000171
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000172#ifdef HAVE_TOUCHSCREEN
Thomas Martitzb673ae22010-10-31 11:11:46 +0000173 if (list->selected_item == 0 || (list->nb_items < nb_lines))
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000174 y_offset = 0; /* reset in case it's a new list */
175
176 int draw_offset = y_offset;
177 /* draw some extra items to not have empty lines at the top and bottom */
178 if (y_offset > 0)
179 {
180 /* make it negative for more consistent apparence when switching
181 * directions */
Thomas Martitzeec89a92013-12-20 23:34:28 +0100182 draw_offset -= linedes.height;
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000183 if (start > 0)
184 start--;
185 }
186 else if (y_offset < 0)
187 end++;
188#else
189 #define draw_offset 0
190#endif
191
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000192 /* draw the scrollbar if its needed */
Thomas Martitzeec89a92013-12-20 23:34:28 +0100193 if (global_settings.scrollbar != SCROLLBAR_OFF)
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000194 {
Thomas Martitzeec89a92013-12-20 23:34:28 +0100195 /* if the scrollbar is shown the text viewport needs to shrink */
196 if (nb_lines < list->nb_items)
Maurus Cuelenaeredaaecb92009-08-17 23:36:49 +0000197 {
Thomas Martitzeec89a92013-12-20 23:34:28 +0100198 struct viewport vp = *list_text_vp;
199 vp.width = SCROLLBAR_WIDTH;
200 vp.height = linedes.height * nb_lines;
Tomer Shalev2438d162009-10-05 19:59:16 +0000201 list_text_vp->width -= SCROLLBAR_WIDTH;
Thomas Martitzeec89a92013-12-20 23:34:28 +0100202 if (scrollbar_in_right)
203 vp.x += list_text_vp->width;
204 else /* left */
205 list_text_vp->x += SCROLLBAR_WIDTH;
Solomon Peachy8b564762021-04-07 06:59:23 -0400206 struct viewport *last = display->set_viewport(&vp);
Thomas Martitzeec89a92013-12-20 23:34:28 +0100207 gui_scrollbar_draw(display,
208 (scrollbar_in_left? 0: 1), 0, SCROLLBAR_WIDTH-1, vp.height,
209 list->nb_items, list_start_item, list_start_item + nb_lines,
210 VERTICAL);
Solomon Peachy8b564762021-04-07 06:59:23 -0400211 display->set_viewport(last);
Maurus Cuelenaeredaaecb92009-08-17 23:36:49 +0000212 }
Thomas Martitzeec89a92013-12-20 23:34:28 +0100213 /* shift everything a bit in relation to the title */
214 else if (!VP_IS_RTL(list_text_vp) && scrollbar_in_left)
215 indent += SCROLLBAR_WIDTH;
Thomas Martitz48bc7622013-01-18 18:02:47 +0100216 else if (VP_IS_RTL(list_text_vp) && scrollbar_in_right)
Thomas Martitzeec89a92013-12-20 23:34:28 +0100217 indent += SCROLLBAR_WIDTH;
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000218 }
Tomer Shalev2438d162009-10-05 19:59:16 +0000219
Georg Gadinger512be372020-12-12 01:11:46 +0100220 display->set_viewport(list_text_vp);
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000221 for (i=start; i<end && i<list->nb_items; i++)
222 {
223 /* do the text */
Thomas Martitzeec89a92013-12-20 23:34:28 +0100224 enum themable_icons icon;
Nils Wallménius3200d042009-08-20 16:47:44 +0000225 unsigned const char *s;
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000226 char entry_buffer[MAX_PATH];
227 unsigned char *entry_name;
228 int text_pos = 0;
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000229 int line = i - start;
Thomas Martitzeec89a92013-12-20 23:34:28 +0100230 int line_indent = 0;
231 int style = STYLE_DEFAULT;
232 bool is_selected = false;
Nils Wallménius68489612008-04-09 15:25:17 +0000233 s = list->callback_get_item_name(i, list->data, entry_buffer,
234 sizeof(entry_buffer));
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000235 entry_name = P2STR(s);
Jonathan Gordon08238172012-07-18 23:19:11 +0200236
237 while (*entry_name == '\t')
238 {
Thomas Martitzeec89a92013-12-20 23:34:28 +0100239 line_indent++;
Jonathan Gordon08238172012-07-18 23:19:11 +0200240 entry_name++;
241 }
Thomas Martitzeec89a92013-12-20 23:34:28 +0100242 if (line_indent)
Jonathan Gordon08238172012-07-18 23:19:11 +0200243 {
Thomas Martitzeec89a92013-12-20 23:34:28 +0100244 if (global_settings.show_icons)
245 line_indent *= list_icon_width(screen);
Jonathan Gordon08238172012-07-18 23:19:11 +0200246 else
Thomas Martitzeec89a92013-12-20 23:34:28 +0100247 line_indent *= display->getcharwidth();
Jonathan Gordon08238172012-07-18 23:19:11 +0200248 }
Thomas Martitzeec89a92013-12-20 23:34:28 +0100249 line_indent += indent;
Jonathan Gordon08238172012-07-18 23:19:11 +0200250
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000251 /* position the string at the correct offset place */
252 int item_width,h;
253 display->getstringsize(entry_name, &item_width, &h);
Tomer Shalev2438d162009-10-05 19:59:16 +0000254 item_offset = gui_list_get_item_offset(list, item_width, text_pos,
255 display, list_text_vp);
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000256
Thomas Martitzac08e692010-09-23 00:02:32 +0000257 /* draw the selected line */
Thomas Martitzf060cd52010-09-23 00:37:33 +0000258 if(
259#ifdef HAVE_TOUCHSCREEN
260 /* don't draw it during scrolling */
Thomas Martitz688302a2012-04-08 13:15:39 +0200261 !hide_selection &&
Thomas Martitzf060cd52010-09-23 00:37:33 +0000262#endif
263 i >= list->selected_item
Thomas Martitzac08e692010-09-23 00:02:32 +0000264 && i < list->selected_item + list->selected_size
265 && list->show_selection_marker)
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000266 {/* The selected item must be displayed scrolling */
William Wilgus43f90742019-11-02 06:15:01 -0500267#ifdef HAVE_LCD_COLOR
268 if (list->selection_color)
269 {
270 /* Display gradient line selector */
271 style = STYLE_GRADIENT;
272 linedes.text_color = list->selection_color->text_color;
273 linedes.line_color = list->selection_color->line_color;
274 linedes.line_end_color = list->selection_color->line_end_color;
275 }
276 else
277#endif
Jonathan Gordon34196ed2008-04-03 04:41:42 +0000278 if (global_settings.cursor_style == 1
279#ifdef HAVE_REMOTE_LCD
Tomer Shalev2438d162009-10-05 19:59:16 +0000280 /* the global_settings.cursor_style check is here to make
Jonathan Gordone385ee12008-12-31 05:59:26 +0000281 * sure if they want the cursor instead of bar it will work
282 */
Jonathan Gordon34196ed2008-04-03 04:41:42 +0000283 || (display->depth < 16 && global_settings.cursor_style)
284#endif
285 )
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000286 {
287 /* Display inverted-line-style */
Jens Arnold3f69bb22009-03-08 12:42:33 +0000288 style = STYLE_INVERT;
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000289 }
290#ifdef HAVE_LCD_COLOR
291 else if (global_settings.cursor_style == 2)
292 {
293 /* Display colour line selector */
Jens Arnold3f69bb22009-03-08 12:42:33 +0000294 style = STYLE_COLORBAR;
Thomas Martitzeec89a92013-12-20 23:34:28 +0100295 linedes.text_color = global_settings.lst_color;
296 linedes.line_color = global_settings.lss_color;
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000297 }
298 else if (global_settings.cursor_style == 3)
299 {
300 /* Display gradient line selector */
Jens Arnold3f69bb22009-03-08 12:42:33 +0000301 style = STYLE_GRADIENT;
Thomas Martitzeec89a92013-12-20 23:34:28 +0100302 linedes.text_color = global_settings.lst_color;
303 linedes.line_color = global_settings.lss_color;
304 linedes.line_end_color = global_settings.lse_color;
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000305 }
306#endif
Thomas Martitzeec89a92013-12-20 23:34:28 +0100307 is_selected = true;
308 }
309
310#ifdef HAVE_LCD_COLOR
311 /* if the list has a color callback */
312 if (list->callback_get_item_color)
313 {
314 int c = list->callback_get_item_color(i, list->data);
315 if (c >= 0)
316 { /* if color selected */
317 linedes.text_color = c;
318 style |= STYLE_COLORED;
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000319 }
320 }
Thomas Martitzeec89a92013-12-20 23:34:28 +0100321#endif
322
323 linedes.style = style;
Frank Gevaerts28591f22017-09-07 10:38:15 +0200324 linedes.scroll = is_selected ? true : list->scroll_all;
Thomas Martitzeec89a92013-12-20 23:34:28 +0100325 linedes.line = i % list->selected_size;
William Wilgus43f90742019-11-02 06:15:01 -0500326 icon = list->callback_get_item_icon ?
327 list->callback_get_item_icon(i, list->data) : Icon_NOICON;
Thomas Martitzeec89a92013-12-20 23:34:28 +0100328 /* the list can have both, one of or neither of cursor and item icons,
329 * if both don't apply icon padding twice between the icons */
330 if (show_cursor && have_icons)
331 put_line(display, 0, line * linedes.height + draw_offset,
332 &linedes, "$*s$"ICON_PADDING_S"I$i$"ICON_PADDING_S"s$*t",
333 line_indent, is_selected ? Icon_Cursor : Icon_NOICON,
334 icon, item_offset, entry_name);
335 else if (show_cursor || have_icons)
336 put_line(display, 0, line * linedes.height + draw_offset,
337 &linedes, "$*s$"ICON_PADDING_S"I$*t", line_indent,
338 show_cursor ? (is_selected ? Icon_Cursor:Icon_NOICON):icon,
339 item_offset, entry_name);
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000340 else
Thomas Martitzeec89a92013-12-20 23:34:28 +0100341 put_line(display, 0, line * linedes.height + draw_offset,
342 &linedes, "$*s$*t", line_indent, item_offset, entry_name);
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000343 }
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000344 display->set_viewport(parent);
345 display->update_viewport();
William Wilgus3237ae42020-10-07 02:01:35 -0400346 display->set_viewport(last_vp);
Jonathan Gordon0e5cec22008-03-05 09:58:30 +0000347}
348
Maurus Cuelenaere1392dc22008-08-23 09:46:38 +0000349#if defined(HAVE_TOUCHSCREEN)
Maurus Cuelenaere94419e92009-08-22 23:35:27 +0000350/* This needs to be fixed if we ever get more than 1 touchscreen on a target. */
Thomas Martitzac08e692010-09-23 00:02:32 +0000351
Thomas Martitz688302a2012-04-08 13:15:39 +0200352/* difference in pixels between draws, above it means enough to start scrolling */
353#define SCROLL_BEGIN_THRESHOLD 3
Thomas Martitzac08e692010-09-23 00:02:32 +0000354
Thomas Martitz688302a2012-04-08 13:15:39 +0200355static enum {
356 SCROLL_NONE, /* no scrolling */
357 SCROLL_BAR, /* scroll by using the scrollbar */
358 SCROLL_SWIPE, /* scroll by wiping over the screen */
359 SCROLL_KINETIC, /* state after releasing swipe */
360} scroll_mode;
Thomas Martitzac08e692010-09-23 00:02:32 +0000361
Thomas Martitzb673ae22010-10-31 11:11:46 +0000362static int scrollbar_scroll(struct gui_synclist * gui_list,
Maurus Cuelenaeredaaecb92009-08-17 23:36:49 +0000363 int y)
364{
Tomer Shalev2438d162009-10-05 19:59:16 +0000365 const int screen = screens[SCREEN_MAIN].screen_type;
Thomas Martitzeec89a92013-12-20 23:34:28 +0100366 const int nb_lines = list_get_nb_lines(gui_list, screen);
Tomer Shalev2438d162009-10-05 19:59:16 +0000367
Maurus Cuelenaeredaaecb92009-08-17 23:36:49 +0000368 if (nb_lines < gui_list->nb_items)
369 {
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000370 /* scrollbar scrolling is still line based */
371 y_offset = 0;
Thomas Martitzeec89a92013-12-20 23:34:28 +0100372 int scrollbar_size = nb_lines*gui_list->line_height[screen];
Maurus Cuelenaeredaaecb92009-08-17 23:36:49 +0000373 int actual_y = y - list_text[screen].y;
374
375 int new_selection = (actual_y * gui_list->nb_items)
376 / scrollbar_size;
377
378 int start_item = new_selection - nb_lines/2;
379 if(start_item < 0)
380 start_item = 0;
381 else if(start_item > gui_list->nb_items - nb_lines)
382 start_item = gui_list->nb_items - nb_lines;
383
384 gui_list->start_item[screen] = start_item;
Maurus Cuelenaeredaaecb92009-08-17 23:36:49 +0000385
386 return ACTION_REDRAW;
387 }
388
389 return ACTION_NONE;
390}
391
Thomas Martitzb673ae22010-10-31 11:11:46 +0000392/* kinetic scrolling, based on
393 *
394 * v = a*t + v0 and ds = v*dt
395 *
396 * In each (fixed interval) timeout, the list is advanced by ds, then
397 * the v is reduced by a.
398 * This way we get a linear and smooth deceleration of the scrolling
399 *
400 * As v is the difference of distance per time unit, v is passed (as
401 * pixels moved since the last call) to the scrolling function which takes
402 * care of the pixel accurate drawing
403 *
404 * v0 is dertermined by averaging the last 4 movements of the list
405 * (the pixel and time difference is used to compute each v)
406 *
407 * influenced by http://stechz.com/tag/kinetic/
408 * We take the easy and smooth first approach (until section "Drawbacks"),
409 * since its drawbacks don't apply for us since our timers seem to be
410 * relatively accurate
411 */
412
413
414#define SIGN(a) ((a) < 0 ? -1 : 1)
415/* these could possibly be configurable */
416/* the lower the smoother */
417#define RELOAD_INTERVAL (HZ/25)
418/* the higher the earler the list stops */
419#define DECELERATION (1000*RELOAD_INTERVAL/HZ)
420
421/* this array holds data to compute the initial velocity v0 */
422static struct kinetic_info {
423 int difference;
424 long ticks;
425} kinetic_data[4];
426static size_t cur_idx;
427
428static struct cb_data {
429 struct gui_synclist *list; /* current list */
430 int velocity; /* in pixel/s */
431} cb_data;
432
433/* data member points to the above struct */
434static struct timeout kinetic_tmo;
435
436static bool is_kinetic_over(void)
437{
438 return !cb_data.velocity && (scroll_mode == SCROLL_KINETIC);
439}
440
Thomas Martitzac08e692010-09-23 00:02:32 +0000441/*
Thomas Martitzb673ae22010-10-31 11:11:46 +0000442 * collect data about how fast the list is moved in order to compute
443 * the initial velocity from it later */
444static void kinetic_stats_collect(const int difference)
445{
446 static long last_tick;
447 /* collect velocity statistics */
448 kinetic_data[cur_idx].difference = difference;
449 kinetic_data[cur_idx].ticks = current_tick - last_tick;
450
451 last_tick = current_tick;
452 cur_idx += 1;
453 if (cur_idx >= ARRAYLEN(kinetic_data))
454 cur_idx = 0; /* rewind the index */
455}
456
457/*
458 * resets the statistic */
459static void kinetic_stats_reset(void)
460{
461 memset(kinetic_data, 0, sizeof(kinetic_data));
462 cur_idx = 0;
463}
464
465/* cancels all currently active kinetic scrolling */
466static void kinetic_force_stop(void)
467{
468 timeout_cancel(&kinetic_tmo);
469 kinetic_stats_reset();
470}
471
472/* helper for gui/list.c to cancel scrolling if a normal button event comes
473 * through dpad or keyboard or whatever */
474void _gui_synclist_stop_kinetic_scrolling(void)
475{
476 y_offset = 0;
477 if (scroll_mode == SCROLL_KINETIC)
478 kinetic_force_stop();
479 scroll_mode = SCROLL_NONE;
Thomas Martitz688302a2012-04-08 13:15:39 +0200480 hide_selection = false;
Thomas Martitzb673ae22010-10-31 11:11:46 +0000481}
482/*
483 * returns false if scrolling should be stopped entirely
484 *
485 * otherwise it returns true even if it didn't actually scroll,
486 * but scrolling mode shouldn't be changed
Thomas Martitzac08e692010-09-23 00:02:32 +0000487 **/
Thomas Martitz33af0de2010-11-10 15:25:15 +0000488
489
490static int scroll_begin_threshold;
491static int threshold_accumulation;
Thomas Martitz688302a2012-04-08 13:15:39 +0200492static bool swipe_scroll(struct gui_synclist * gui_list, int difference)
Thomas Martitzac08e692010-09-23 00:02:32 +0000493{
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000494 /* fixme */
495 const enum screen_type screen = screens[SCREEN_MAIN].screen_type;
Thomas Martitzeec89a92013-12-20 23:34:28 +0100496 const int nb_lines = list_get_nb_lines(gui_list, screen);
497 const int line_height = gui_list->line_height[screen];
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000498
Thomas Martitz33af0de2010-11-10 15:25:15 +0000499 if (UNLIKELY(scroll_begin_threshold == 0))
500 scroll_begin_threshold = touchscreen_get_scroll_threshold();
501
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000502 /* make selecting items easier */
Thomas Martitz33af0de2010-11-10 15:25:15 +0000503 threshold_accumulation += abs(difference);
504 if (threshold_accumulation < scroll_begin_threshold && scroll_mode == SCROLL_NONE)
Thomas Martitzb673ae22010-10-31 11:11:46 +0000505 return false;
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000506
Thomas Martitz33af0de2010-11-10 15:25:15 +0000507 threshold_accumulation = 0;
508
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000509 /* does the list even scroll? if no, return but still show
510 * the caller that we would scroll */
511 if (nb_lines >= gui_list->nb_items)
Thomas Martitzb673ae22010-10-31 11:11:46 +0000512 return true;
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000513
514 const int old_start = gui_list->start_item[screen];
515 int new_start_item = -1;
516 int line_diff = 0;
517
518 /* don't scroll at the edges of the list */
519 if ((old_start == 0 && difference > 0)
520 || (old_start == (gui_list->nb_items - nb_lines) && difference < 0))
Thomas Martitzac08e692010-09-23 00:02:32 +0000521 {
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000522 y_offset = 0;
Thomas Martitzb673ae22010-10-31 11:11:46 +0000523 gui_list->start_item[screen] = old_start;
524 return scroll_mode != SCROLL_KINETIC; /* stop kinetic at the edges */
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000525 }
526
527 /* add up y_offset over time and translate to lines
528 * if scrolled enough */
529 y_offset += difference;
530 if (abs(y_offset) > line_height)
531 {
532 line_diff = y_offset/line_height;
533 y_offset -= line_diff * line_height;
534 }
535
536 if(line_diff != 0)
537 {
Thomas Martitzb673ae22010-10-31 11:11:46 +0000538 int selection_offset = gui_list->selected_item - old_start;
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000539 new_start_item = old_start - line_diff;
Jonathan Gordon0dd8ba42010-09-26 07:32:16 +0000540 /* check if new_start_item is bigger than list item count */
Thomas Martitzac08e692010-09-23 00:02:32 +0000541 if(new_start_item > gui_list->nb_items - nb_lines)
542 new_start_item = gui_list->nb_items - nb_lines;
Jonathan Gordon0dd8ba42010-09-26 07:32:16 +0000543 /* set new_start_item to 0 if it's negative */
Thomas Martitzac08e692010-09-23 00:02:32 +0000544 if(new_start_item < 0)
545 new_start_item = 0;
Thomas Martitzb673ae22010-10-31 11:11:46 +0000546
Thomas Martitzac08e692010-09-23 00:02:32 +0000547 gui_list->start_item[screen] = new_start_item;
Thomas Martitzb673ae22010-10-31 11:11:46 +0000548 /* keep selected item in sync */
549 gui_list->selected_item = new_start_item + selection_offset;
Thomas Martitzac08e692010-09-23 00:02:32 +0000550 }
Thomas Martitzd9d0b4d2010-10-06 12:46:42 +0000551
Thomas Martitzb673ae22010-10-31 11:11:46 +0000552 return true;
553}
554
555static int kinetic_callback(struct timeout *tmo)
556{
557 /* cancel if screen was pressed */
558 if (scroll_mode != SCROLL_KINETIC)
559 return 0;
560
561 struct cb_data *data = (struct cb_data*)tmo->data;
Thomas Martitzb673ae22010-10-31 11:11:46 +0000562 /* ds = v*dt */
563 int pixel_diff = data->velocity * RELOAD_INTERVAL / HZ;
564 /* remember signedness to detect stopping */
565 int old_sign = SIGN(data->velocity);
566 /* advance the list */
Thomas Martitz688302a2012-04-08 13:15:39 +0200567 if (!swipe_scroll(data->list, pixel_diff))
Thomas Martitzb673ae22010-10-31 11:11:46 +0000568 {
569 /* nothing to scroll? */
570 data->velocity = 0;
571 }
572 else
573 {
574 /* decelerate by a fixed amount
575 * decrementing v0 over time by the deceleration is
576 * equivalent to computing v = a*t + v0 */
577 data->velocity -= SIGN(data->velocity)*DECELERATION;
578 if (SIGN(data->velocity) != old_sign)
579 data->velocity = 0;
580 }
581
Thomas Martitz688302a2012-04-08 13:15:39 +0200582 /* let get_action() timeout, which loads to a
583 * gui_synclist_draw() call from the main thread */
Thomas Martitzbb0e4cc2012-04-05 13:00:05 +0200584 queue_post(&button_queue, BUTTON_REDRAW, 0);
Thomas Martitzb673ae22010-10-31 11:11:46 +0000585 /* stop if the velocity hit or crossed zero */
586 if (!data->velocity)
587 {
588 kinetic_stats_reset();
589 return 0;
590 }
Thomas Martitzb673ae22010-10-31 11:11:46 +0000591 return RELOAD_INTERVAL; /* cancel or reload */
592}
593
594/*
595 * computes the initial velocity v0 and sets up the timer */
596static bool kinetic_setup_scroll(struct gui_synclist *list)
597{
598 /* compute initial velocity */
599 int i, _i, v0, len = ARRAYLEN(kinetic_data);
600 for(i = 0, _i = 0, v0 = 0; i < len; i++)
601 { /* in pixel/s */
602 if (kinetic_data[i].ticks > 0)
603 {
604 v0 += kinetic_data[i].difference*HZ/kinetic_data[i].ticks;
605 _i++;
606 }
607 }
608 if (_i > 0)
609 v0 /= _i;
610 else
611 v0 = 0;
612
613 if (v0 != 0)
614 {
615 cb_data.list = list;
616 cb_data.velocity = v0;
617 timeout_register(&kinetic_tmo, kinetic_callback, RELOAD_INTERVAL, (intptr_t)&cb_data);
618 return true;
619 }
620 return false;
Thomas Martitzac08e692010-09-23 00:02:32 +0000621}
622
Thomas Martitz688302a2012-04-08 13:15:39 +0200623#define OUTSIDE 0
624#define TITLE_TEXT (1<<0)
625#define TITLE_ICON (1<<1)
626#define SCROLLBAR (1<<2)
627#define LIST_TEXT (1<<3)
628#define LIST_ICON (1<<4)
629
630#define TITLE (TITLE_TEXT|TITLE_ICON)
631#define LIST (LIST_TEXT|LIST_ICON)
632
633static int get_click_location(struct gui_synclist *list, int x, int y)
Jonathan Gordonf444f1e2008-03-05 10:38:10 +0000634{
Thomas Martitz688302a2012-04-08 13:15:39 +0200635 int screen = SCREEN_MAIN;
636 struct viewport *parent, *title, *text;
637 int retval = OUTSIDE;
Maurus Cuelenaere5da84ec2010-06-24 14:32:25 +0000638
Thomas Martitz688302a2012-04-08 13:15:39 +0200639 parent = list->parent[screen];
640 if (viewport_point_within_vp(parent, x, y))
Thomas Martitzb673ae22010-10-31 11:11:46 +0000641 {
Thomas Martitz688302a2012-04-08 13:15:39 +0200642 /* see if the title was clicked */
643 title = &title_text[screen];
644 if (viewport_point_within_vp(title, x, y))
645 retval = TITLE_TEXT;
646 /* check the icon too */
647 if (list->title_icon != Icon_NOICON && global_settings.show_icons)
Jonathan Gordonf444f1e2008-03-05 10:38:10 +0000648 {
Thomas Martitz688302a2012-04-08 13:15:39 +0200649 int width = list_icon_width(screen);
650 struct viewport vp = *title;
651 if (VP_IS_RTL(&vp))
652 vp.x += vp.width;
653 else
654 vp.x -= width;
655 vp.width = width;
656 if (viewport_point_within_vp(&vp, x, y))
657 retval = TITLE_ICON;
658 }
659 /* check scrollbar. assume it's shown, if it isn't it will be handled
660 * later */
661 if (retval == OUTSIDE)
662 {
663 bool on_scrollbar_clicked;
664 int adj_x = x - parent->x;
665 switch (global_settings.scrollbar)
Thomas Martitzb673ae22010-10-31 11:11:46 +0000666 {
Thomas Martitz688302a2012-04-08 13:15:39 +0200667 case SCROLLBAR_LEFT:
668 on_scrollbar_clicked = adj_x <= SCROLLBAR_WIDTH; break;
669 case SCROLLBAR_RIGHT:
670 on_scrollbar_clicked = adj_x > (title->x + title->width - SCROLLBAR_WIDTH); break;
671 default:
672 on_scrollbar_clicked = false; break;
Thomas Martitzb673ae22010-10-31 11:11:46 +0000673 }
Thomas Martitz688302a2012-04-08 13:15:39 +0200674 if (on_scrollbar_clicked)
675 retval = SCROLLBAR;
Jonathan Gordonf444f1e2008-03-05 10:38:10 +0000676 }
Thomas Martitz688302a2012-04-08 13:15:39 +0200677 if (retval == OUTSIDE)
Thomas Martitzb673ae22010-10-31 11:11:46 +0000678 {
Thomas Martitz688302a2012-04-08 13:15:39 +0200679 text = &list_text[screen];
680 if (viewport_point_within_vp(text, x, y))
681 retval = LIST_TEXT;
682 else /* if all fails, it must be on the list icons */
683 retval = LIST_ICON;
Thomas Martitzb673ae22010-10-31 11:11:46 +0000684 }
Jonathan Gordonf444f1e2008-03-05 10:38:10 +0000685 }
Thomas Martitz688302a2012-04-08 13:15:39 +0200686 return retval;
687}
688
689unsigned gui_synclist_do_touchscreen(struct gui_synclist * list)
690{
691 enum screen_type screen;
692 struct viewport *parent;
693 short x, y;
Frank Gevaerts71511112012-06-13 23:07:49 +0200694 int action, adj_x, adj_y, line, line_height, list_start_item;
Thomas Martitz688302a2012-04-08 13:15:39 +0200695 bool recurse;
Thomas Martitz2ffde902013-01-17 23:26:37 +0100696 static bool initial_touch = true;
697 static int last_y;
Thomas Martitz688302a2012-04-08 13:15:39 +0200698
699 screen = SCREEN_MAIN;
700 parent = list->parent[screen];
Thomas Martitzeec89a92013-12-20 23:34:28 +0100701 line_height = list->line_height[screen];
Thomas Martitz688302a2012-04-08 13:15:39 +0200702 list_start_item = list->start_item[screen];
703 /* start with getting the action code and finding the click location */
704 action = action_get_touchscreen_press(&x, &y);
Frank Gevaerts71511112012-06-13 23:07:49 +0200705 adj_x = x - parent->x;
Thomas Martitz688302a2012-04-08 13:15:39 +0200706 adj_y = y - parent->y;
707
Thomas Martitz688302a2012-04-08 13:15:39 +0200708
709 /* some defaults before running the state machine */
710 recurse = false;
Thomas Martitz2760d572012-06-15 13:00:20 +0200711 hide_selection = false;
Thomas Martitz688302a2012-04-08 13:15:39 +0200712
713 switch (scroll_mode)
Jonathan Gordonf444f1e2008-03-05 10:38:10 +0000714 {
Thomas Martitz688302a2012-04-08 13:15:39 +0200715 case SCROLL_NONE:
Thomas Martitzb39a4932010-10-09 11:22:52 +0000716 {
Thomas Martitz2ffde902013-01-17 23:26:37 +0100717 int click_loc;
718 if (initial_touch)
Thomas Martitzb673ae22010-10-31 11:11:46 +0000719 {
Thomas Martitz2ffde902013-01-17 23:26:37 +0100720 /* on the first touch last_y has to be reset to avoid
721 * glitches with touches from long ago */
722 last_y = adj_y;
723 initial_touch = false;
724 }
725
726 line = 0; /* silence gcc 'used uninitialized' warning */
727 click_loc = get_click_location(list, x, y);
728 if (click_loc & LIST)
729 {
730 if(!skinlist_get_item(&screens[screen], list, adj_x, adj_y, &line))
731 {
732 /* selection needs to be corrected if items are only partially visible */
733 line = (adj_y - y_offset) / line_height;
734 if (list_display_title(list, screen))
735 line -= 1; /* adjust for the list title */
736 }
Aidan MacDonald6da49002021-06-22 21:39:45 +0100737 if (list_start_item+line >= list->nb_items)
Thomas Martitz2ffde902013-01-17 23:26:37 +0100738 return ACTION_NONE;
739 list->selected_item = list_start_item+line;
740
741 gui_synclist_speak_item(list);
742 }
743 if (action == BUTTON_TOUCHSCREEN)
744 {
745 /* if not scrolling, the user is trying to select */
746 int diff = adj_y - last_y;
747 if ((click_loc & LIST) && swipe_scroll(list, diff))
748 scroll_mode = SCROLL_SWIPE;
749 else if (click_loc & SCROLLBAR)
750 scroll_mode = SCROLL_BAR;
751 }
752 else if (action == BUTTON_REPEAT)
753 {
Thomas Martitzd3ead232012-04-10 10:08:30 +0200754 if (click_loc & LIST)
755 {
Thomas Martitz2ffde902013-01-17 23:26:37 +0100756 /* held a single line for a while, bring up the context menu */
757 gui_synclist_select_item(list, list_start_item + line);
758 /* don't sent context repeatedly */
759 action_wait_for_release();
760 initial_touch = true;
761 return ACTION_STD_CONTEXT;
Thomas Martitzd3ead232012-04-10 10:08:30 +0200762 }
Thomas Martitz2ffde902013-01-17 23:26:37 +0100763 }
764 else if (action & BUTTON_REL)
765 {
766 initial_touch = true;
767 if (click_loc & LIST)
768 { /* release on list item enters it */
769 gui_synclist_select_item(list, list_start_item + line);
770 return ACTION_STD_OK;
Thomas Martitz688302a2012-04-08 13:15:39 +0200771 }
Thomas Martitz2ffde902013-01-17 23:26:37 +0100772 else if (click_loc & TITLE_TEXT)
773 { /* clicking the title goes one level up (cancel) */
774 return ACTION_STD_CANCEL;
Thomas Martitz688302a2012-04-08 13:15:39 +0200775 }
Thomas Martitz2ffde902013-01-17 23:26:37 +0100776 else if (click_loc & TITLE_ICON)
777 { /* clicking the title icon goes back to the root */
778 return ACTION_STD_MENU;
Thomas Martitz688302a2012-04-08 13:15:39 +0200779 }
780 }
781 break;
782 }
783 case SCROLL_SWIPE:
784 {
785 /* when swipe scrolling, we accept outside presses as well and
786 * grab the entire screen (i.e. click_loc does not matter) */
787 int diff = adj_y - last_y;
Thomas Martitz2760d572012-06-15 13:00:20 +0200788 hide_selection = true;
Thomas Martitz688302a2012-04-08 13:15:39 +0200789 kinetic_stats_collect(diff);
790 if (swipe_scroll(list, diff))
791 {
792 /* letting the pen go enters kinetic scrolling */
793 if ((action & BUTTON_REL))
794 {
795 if (kinetic_setup_scroll(list))
Thomas Martitz2760d572012-06-15 13:00:20 +0200796 {
797 hide_selection = true;
Thomas Martitz688302a2012-04-08 13:15:39 +0200798 scroll_mode = SCROLL_KINETIC;
Thomas Martitz2760d572012-06-15 13:00:20 +0200799 }
Thomas Martitz688302a2012-04-08 13:15:39 +0200800 else
801 scroll_mode = SCROLL_NONE;
802 }
803 }
804 else if (action & BUTTON_REL)
Thomas Martitz688302a2012-04-08 13:15:39 +0200805 scroll_mode = SCROLL_NONE;
Thomas Martitz48c248d2012-04-08 13:28:21 +0200806
807 if (scroll_mode == SCROLL_NONE)
Thomas Martitz2ffde902013-01-17 23:26:37 +0100808 initial_touch = true;
Thomas Martitz688302a2012-04-08 13:15:39 +0200809 break;
810 }
811 case SCROLL_KINETIC:
812 {
813 /* during kinetic scrolling we need to handle cancellation.
814 * This state is actually only entered upon end of it as this
815 * function is not called during the animation. */
816 if (!is_kinetic_over())
817 { /* a) the user touched the screen (manual cancellation) */
Thomas Martitzb673ae22010-10-31 11:11:46 +0000818 kinetic_force_stop();
Thomas Martitz42699342012-04-17 13:52:21 +0200819 if (get_click_location(list, x, y) & SCROLLBAR)
820 scroll_mode = SCROLL_BAR;
821 else
822 scroll_mode = SCROLL_SWIPE;
Thomas Martitzb673ae22010-10-31 11:11:46 +0000823 }
Jonathan Gordonf444f1e2008-03-05 10:38:10 +0000824 else
Thomas Martitz688302a2012-04-08 13:15:39 +0200825 { /* b) kinetic scrolling stopped on its own */
826 /* need to re-run this with SCROLL_NONE since otherwise
827 * the next touch is not detected correctly */
828 scroll_mode = SCROLL_NONE;
829 recurse = true;
Thomas Martitzac08e692010-09-23 00:02:32 +0000830 }
Thomas Martitz688302a2012-04-08 13:15:39 +0200831 break;
832 }
833 case SCROLL_BAR:
834 {
Thomas Martitz2760d572012-06-15 13:00:20 +0200835 hide_selection = true;
Thomas Martitz688302a2012-04-08 13:15:39 +0200836 /* similarly to swipe scroll, using the scrollbar grabs
837 * focus so the click location is irrelevant */
838 scrollbar_scroll(list, adj_y);
839 if (action & BUTTON_REL)
840 scroll_mode = SCROLL_NONE;
841 break;
Jonathan Gordonf444f1e2008-03-05 10:38:10 +0000842 }
Jonathan Gordonf444f1e2008-03-05 10:38:10 +0000843 }
Thomas Martitz688302a2012-04-08 13:15:39 +0200844
Thomas Martitz2ffde902013-01-17 23:26:37 +0100845 /* register y position unless forcefully reset */
846 if (!initial_touch)
Thomas Martitz688302a2012-04-08 13:15:39 +0200847 last_y = adj_y;
848
849 return recurse ? gui_synclist_do_touchscreen(list) : ACTION_REDRAW;
Jonathan Gordonf444f1e2008-03-05 10:38:10 +0000850}
Thomas Martitz688302a2012-04-08 13:15:39 +0200851
Jonathan Gordonf444f1e2008-03-05 10:38:10 +0000852#endif