A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007 by Jonathan Gordon
11 *
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.
16 *
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"
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"
33
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"
44#include "line.h"
45
46#define ICON_PADDING 1
47#define ICON_PADDING_S "1"
48
49/* these are static to make scrolling work */
50static struct viewport list_text[NB_SCREENS], title_text[NB_SCREENS];
51
52#ifdef HAVE_TOUCHSCREEN
53static bool hide_selection;
54#endif
55
56/* list-private helpers from the generic list.c (move to header?) */
57int gui_list_get_item_offset(struct gui_synclist * gui_list, int item_width,
58 int text_pos, struct screen * display,
59 struct viewport *vp);
60bool list_display_title(struct gui_synclist *list, enum screen_type screen);
61int list_get_nb_lines(struct gui_synclist *list, enum screen_type screen);
62
63void gui_synclist_scroll_stop(struct gui_synclist *lists)
64{
65 FOR_NB_SCREENS(i)
66 {
67 screens[i].scroll_stop_viewport(&list_text[i]);
68 screens[i].scroll_stop_viewport(&title_text[i]);
69 screens[i].scroll_stop_viewport(lists->parent[i]);
70 }
71}
72
73/* Draw the list...
74 internal screen layout:
75 -----------------
76 |TI| title | TI is title icon
77 -----------------
78 | | | |
79 |S|I| | S - scrollbar
80 | | | items | I - icons
81 | | | |
82 ------------------
83
84 Note: This image is flipped horizontally when the language is a
85 right-to-left one (Hebrew, Arabic)
86*/
87
88static int list_icon_width(enum screen_type screen)
89{
90 return get_icon_width(screen) + ICON_PADDING * 2;
91}
92
93static void _default_listdraw_fn(struct list_putlineinfo_t *list_info)
94{
95 struct screen *display = list_info->display;
96 int x = list_info->x;
97 int y = list_info->y;
98 int item_indent = list_info->item_indent;
99 int item_offset = list_info->item_offset;
100 int icon = list_info->icon;
101 bool is_selected = list_info->is_selected;
102 bool is_title = list_info->is_title;
103 bool show_cursor = list_info->show_cursor;
104 bool have_icons = list_info->have_icons;
105 struct line_desc *linedes = list_info->linedes;
106 const char *dsp_text = list_info->dsp_text;
107
108 if (is_title)
109 {
110 if (have_icons)
111 display->put_line(x, y, linedes, "$"ICON_PADDING_S"I$t",
112 icon, dsp_text);
113 else
114 display->put_line(x, y, linedes, "$t", dsp_text);
115 }
116 else if (show_cursor && have_icons)
117 {
118 /* the list can have both, one of or neither of cursor and item icons,
119 * if both don't apply icon padding twice between the icons */
120 display->put_line(x, y,
121 linedes, "$*s$"ICON_PADDING_S"I$i$"ICON_PADDING_S"s$*t",
122 item_indent, is_selected ? Icon_Cursor : Icon_NOICON,
123 icon, item_offset, dsp_text);
124 }
125 else if (show_cursor || have_icons)
126 {
127 display->put_line(x, y, linedes, "$*s$"ICON_PADDING_S"I$*t", item_indent,
128 show_cursor ? (is_selected ? Icon_Cursor:Icon_NOICON):icon,
129 item_offset, dsp_text);
130 }
131 else
132 {
133 display->put_line(x, y, linedes, "$*s$*t", item_indent, item_offset, dsp_text);
134 }
135}
136
137static bool draw_title(struct screen *display,
138 struct gui_synclist *list,
139 list_draw_item *callback_draw_item)
140{
141 const int screen = display->screen_type;
142 struct viewport *title_text_vp = &title_text[screen];
143 struct line_desc linedes = LINE_DESC_DEFINIT;
144
145 if (sb_set_title_text(list->title, list->title_icon, screen))
146 return false; /* the sbs is handling the title */
147 display->scroll_stop_viewport(title_text_vp);
148 if (!list_display_title(list, screen))
149 return false;
150 *title_text_vp = *(list->parent[screen]);
151 linedes.height = list->line_height[screen];
152 title_text_vp->height = linedes.height;
153
154#if LCD_DEPTH > 1
155 /* XXX: Do we want to support the separator on remote displays? */
156 if (display->screen_type == SCREEN_MAIN && global_settings.list_separator_height != 0)
157 linedes.separator_height = abs(global_settings.list_separator_height)
158 + (lcd_get_dpi() > 200 ? 2 : 1);
159#endif
160
161#ifdef HAVE_LCD_COLOR
162 if (list->title_color >= 0)
163 linedes.style |= (STYLE_COLORED|list->title_color);
164#endif
165 linedes.scroll = true;
166
167 display->set_viewport(title_text_vp);
168 int icon = list->title_icon;
169 int icon_w = list_icon_width(display->screen_type);
170 bool have_icons = false;
171 if (icon != Icon_NOICON && list->show_icons)
172 {
173 have_icons = true;
174 }
175
176 struct list_putlineinfo_t list_info =
177 {
178 .x = 0, .y = 0, .item_indent = 0, .item_offset = 0,
179 .line = -1, .icon = icon, .icon_width = icon_w,
180 .display = display, .vp = title_text_vp, .linedes = &linedes, .list = list,
181 .dsp_text = list->title,
182 .is_selected = false, .is_title = true, .show_cursor = false,
183 .have_icons = have_icons
184 };
185 callback_draw_item(&list_info);
186
187 return true;
188}
189
190void list_draw(struct screen *display, struct gui_synclist *list)
191{
192 int start, end, item_offset, i;
193 const int screen = display->screen_type;
194 list_draw_item *callback_draw_item;
195
196 const int list_start_item = list->start_item[screen];
197 const bool scrollbar_in_left = (list->scrollbar == SCROLLBAR_LEFT);
198 const bool scrollbar_in_right = (list->scrollbar == SCROLLBAR_RIGHT);
199 const bool show_cursor = (list->cursor_style == SYNCLIST_CURSOR_NOSTYLE);
200 const bool have_icons = list->callback_get_item_icon && list->show_icons;
201
202 struct viewport *parent = (list->parent[screen]);
203 struct line_desc linedes = LINE_DESC_DEFINIT;
204 bool show_title;
205 struct viewport *list_text_vp = &list_text[screen];
206 int indent = 0;
207
208 if (list->callback_draw_item != NULL)
209 callback_draw_item = list->callback_draw_item;
210 else
211 callback_draw_item = _default_listdraw_fn;
212
213 struct viewport * last_vp = display->set_viewport(parent);
214 display->clear_viewport();
215 if (!list->scroll_all)
216 display->scroll_stop_viewport(list_text_vp);
217 *list_text_vp = *parent;
218 if ((show_title = draw_title(display, list, callback_draw_item)))
219 {
220 int title_height = title_text[screen].height;
221 list_text_vp->y += title_height;
222 list_text_vp->height -= title_height;
223 }
224
225 const int nb_lines = list_get_nb_lines(list, screen);
226
227 linedes.height = list->line_height[screen];
228 linedes.nlines = list->selected_size;
229#if LCD_DEPTH > 1
230 /* XXX: Do we want to support the separator on remote displays? */
231 if (display->screen_type == SCREEN_MAIN)
232 linedes.separator_height = abs(global_settings.list_separator_height);
233#endif
234 start = list_start_item;
235 end = start + nb_lines;
236
237#ifdef HAVE_TOUCHSCREEN
238 /* y_pos needs to be clamped now since it can overflow the maximum
239 * in some cases, and we have no easy way to prevent this beforehand */
240 int max_y_pos = list->nb_items * linedes.height - list_text[screen].height;
241 if (max_y_pos > 0 && list->y_pos > max_y_pos)
242 list->y_pos = max_y_pos;
243
244 int draw_offset = list_start_item * linedes.height - list->y_pos;
245 /* draw some extra items to not have empty lines at the top and bottom */
246 if (draw_offset > 0)
247 {
248 /* make it negative for more consistent apparence when switching
249 * directions */
250 draw_offset -= linedes.height;
251 if (start > 0)
252 start--;
253 }
254 else if (draw_offset < 0) {
255 if(end < list->nb_items)
256 end++;
257 }
258
259 /* If the viewport is not an exact multiple of the line height, then
260 * there will be space for one more partial line. */
261 int spare_space = list_text_vp->height - linedes.height * nb_lines;
262 if(nb_lines < list->nb_items && spare_space > 0 && end < list->nb_items)
263 if(end < list->nb_items)
264 end++;
265#else
266 #define draw_offset 0
267#endif
268
269 /* draw the scrollbar if its needed */
270 if (list->scrollbar != SCROLLBAR_OFF)
271 {
272 /* if the scrollbar is shown the text viewport needs to shrink */
273 if (nb_lines < list->nb_items)
274 {
275 struct viewport vp = *list_text_vp;
276 vp.width = SCROLLBAR_WIDTH;
277#ifndef HAVE_TOUCHSCREEN
278 /* touchscreens must use full viewport height
279 * due to pixelwise rendering */
280 vp.height = linedes.height * nb_lines;
281#endif
282 list_text_vp->width -= SCROLLBAR_WIDTH;
283 if (scrollbar_in_right)
284 vp.x += list_text_vp->width;
285 else /* left */
286 list_text_vp->x += SCROLLBAR_WIDTH;
287 struct viewport *last = display->set_viewport(&vp);
288
289#ifndef HAVE_TOUCHSCREEN
290 /* button targets go itemwise */
291 int scrollbar_items = list->nb_items;
292 int scrollbar_min = list_start_item;
293 int scrollbar_max = list_start_item + nb_lines;
294#else
295 /* touchscreens use pixelwise scrolling */
296 int scrollbar_items = list->nb_items * linedes.height;
297 int scrollbar_min = list->y_pos;
298 int scrollbar_max = list->y_pos + list_text_vp->height;
299#endif
300 gui_scrollbar_draw(display,
301 (scrollbar_in_left? 0: 1), 0, SCROLLBAR_WIDTH-1, vp.height,
302 scrollbar_items, scrollbar_min, scrollbar_max, VERTICAL);
303 display->set_viewport(last);
304 }
305 /* shift everything a bit in relation to the title */
306 else if (!VP_IS_RTL(list_text_vp) && scrollbar_in_left)
307 indent += SCROLLBAR_WIDTH;
308 else if (VP_IS_RTL(list_text_vp) && scrollbar_in_right)
309 indent += SCROLLBAR_WIDTH;
310 }
311
312 display->set_viewport(list_text_vp);
313 int icon_w = list_icon_width(screen);
314 int character_width = display->getcharwidth();
315
316 struct list_putlineinfo_t list_info =
317 {
318 .x = 0, .y = 0, .vp = list_text_vp, .list = list,
319 .icon_width = icon_w, .is_title = false, .show_cursor = show_cursor,
320 .have_icons = have_icons, .linedes = &linedes, .display = display
321 };
322
323 for (i=start; i<end && i<list->nb_items; i++)
324 {
325 /* do the text */
326 enum themable_icons icon;
327 unsigned const char *s;
328 extern char simplelist_buffer[SIMPLELIST_MAX_LINES * SIMPLELIST_MAX_LINELENGTH];
329 /*char entry_buffer[MAX_PATH]; use the buffer from gui/list.c instead */
330 unsigned char *entry_name;
331 int line = i - start;
332 int line_indent = 0;
333 int style = STYLE_DEFAULT;
334 bool is_selected = false;
335 s = list->callback_get_item_name(i, list->data, simplelist_buffer,
336 sizeof(simplelist_buffer));
337 if (P2ID((unsigned char *)s) > VOICEONLY_DELIMITER)
338 entry_name = "";
339 else
340 entry_name = P2STR(s);
341
342 while (*entry_name == '\t')
343 {
344 line_indent++;
345 entry_name++;
346 }
347 if (line_indent)
348 {
349 if (list->show_icons)
350 line_indent *= icon_w;
351 else
352 line_indent *= character_width;
353 }
354 line_indent += indent;
355
356 /* position the string at the correct offset place */
357 int item_width,h;
358 display->getstringsize(entry_name, &item_width, &h);
359 item_offset = gui_list_get_item_offset(list, item_width, indent + (list->show_icons ? icon_w : 0),
360 display, list_text_vp);
361
362 /* draw the selected line */
363 if(
364#ifdef HAVE_TOUCHSCREEN
365 /* don't draw it during scrolling */
366 !hide_selection &&
367#endif
368 i >= list->selected_item
369 && i < list->selected_item + list->selected_size)
370 {/* The selected item must be displayed scrolling */
371#ifdef HAVE_LCD_COLOR
372 if (list->selection_color)
373 {
374 /* Display gradient line selector */
375 style = STYLE_GRADIENT;
376 linedes.text_color = list->selection_color->text_color;
377 linedes.line_color = list->selection_color->line_color;
378 linedes.line_end_color = list->selection_color->line_end_color;
379 }
380 else
381#endif
382 if (list->cursor_style == SYNCLIST_CURSOR_INVERT
383#ifdef HAVE_REMOTE_LCD
384 /* the global_settings.cursor_style check is here to make
385 * sure if they want the cursor instead of bar it will work
386 */
387 || (display->depth < 16 && list->cursor_style)
388#endif
389 )
390 {
391 /* Display inverted-line-style */
392 style = STYLE_INVERT;
393 }
394#ifdef HAVE_LCD_COLOR
395 else if (list->cursor_style == SYNCLIST_CURSOR_COLOR)
396 {
397 /* Display colour line selector */
398 style = STYLE_COLORBAR;
399 linedes.text_color = global_settings.lst_color;
400 linedes.line_color = global_settings.lss_color;
401 }
402 else if (list->cursor_style == SYNCLIST_CURSOR_GRADIENT)
403 {
404 /* Display gradient line selector */
405 style = STYLE_GRADIENT;
406 linedes.text_color = global_settings.lst_color;
407 linedes.line_color = global_settings.lss_color;
408 linedes.line_end_color = global_settings.lse_color;
409 }
410#endif
411 is_selected = true;
412 }
413
414#ifdef HAVE_LCD_COLOR
415 /* if the list has a color callback */
416 if (list->callback_get_item_color)
417 {
418 int c = list->callback_get_item_color(i, list->data);
419 if (c >= 0)
420 { /* if color selected */
421 linedes.text_color = c;
422 style |= STYLE_COLORED;
423 }
424 }
425#endif
426 linedes.style = style;
427 linedes.scroll = is_selected ? true : list->scroll_all;
428 linedes.line = i % list->selected_size;
429 icon = list->callback_get_item_icon ?
430 list->callback_get_item_icon(i, list->data) : Icon_NOICON;
431
432
433 list_info.y = line * linedes.height + draw_offset;
434 list_info.is_selected = is_selected;
435 list_info.item_indent = line_indent;
436 list_info.line = i;
437 list_info.icon = icon;
438 list_info.dsp_text = entry_name;
439 list_info.item_offset = item_offset;
440
441 callback_draw_item(&list_info);
442 }
443 display->set_viewport(parent);
444 display->update_viewport();
445 display->set_viewport(last_vp);
446}
447
448#if defined(HAVE_TOUCHSCREEN)
449/* This needs to be fixed if we ever get more than 1 touchscreen on a target. */
450
451/* difference in pixels between draws, above it means enough to start scrolling */
452#define SCROLL_BEGIN_THRESHOLD 3
453
454static enum {
455 SCROLL_NONE, /* no scrolling */
456 SCROLL_BAR, /* scroll by using the scrollbar */
457 SCROLL_SWIPE, /* scroll by wiping over the screen */
458 SCROLL_KINETIC, /* state after releasing swipe */
459} scroll_mode;
460
461static int scrollbar_scroll(struct gui_synclist * gui_list, int y)
462{
463 const int screen = screens[SCREEN_MAIN].screen_type;
464 const int nb_lines = list_get_nb_lines(gui_list, screen);
465
466 if (nb_lines < gui_list->nb_items)
467 {
468 const int line_height = gui_list->line_height[screen];
469
470 /* try to position the center of the scrollbar at the touch point */
471 int scrollbar_size = list_text[screen].height;
472 int actual_y = y - list_text[screen].y;
473 int new_y_pos = (actual_y * gui_list->nb_items * line_height) / scrollbar_size;
474 int new_start = (actual_y * gui_list->nb_items) / scrollbar_size;
475
476 new_start -= nb_lines / 2;
477 new_y_pos -= (nb_lines * line_height) / 2;
478 if(new_start < 0) {
479 new_start = 0;
480 new_y_pos = 0;
481 } else if(new_start > gui_list->nb_items - nb_lines) {
482 new_start = gui_list->nb_items - nb_lines;
483 new_y_pos = new_start * line_height;
484 }
485
486 gui_list->start_item[screen] = new_start;
487 gui_list->y_pos = new_y_pos;
488
489 return ACTION_REDRAW;
490 }
491
492 return ACTION_NONE;
493}
494
495/* kinetic scrolling, based on
496 *
497 * v = a*t + v0 and ds = v*dt
498 *
499 * In each (fixed interval) timeout, the list is advanced by ds, then
500 * the v is reduced by a.
501 * This way we get a linear and smooth deceleration of the scrolling
502 *
503 * As v is the difference of distance per time unit, v is passed (as
504 * pixels moved since the last call) to the scrolling function which takes
505 * care of the pixel accurate drawing
506 *
507 * v0 is dertermined by averaging the last 4 movements of the list
508 * (the pixel and time difference is used to compute each v)
509 *
510 * influenced by http://stechz.com/tag/kinetic/
511 * We take the easy and smooth first approach (until section "Drawbacks"),
512 * since its drawbacks don't apply for us since our timers seem to be
513 * relatively accurate
514 */
515
516
517#define SIGN(a) ((a) < 0 ? -1 : 1)
518/* these could possibly be configurable */
519/* the lower the smoother */
520#define RELOAD_INTERVAL (HZ/25)
521/* the higher the earler the list stops */
522#define DECELERATION (1000*RELOAD_INTERVAL/HZ)
523
524/* this array holds data to compute the initial velocity v0 */
525static struct kinetic_info {
526 int difference;
527 long ticks;
528} kinetic_data[4];
529static size_t cur_idx;
530
531static struct cb_data {
532 struct gui_synclist *list; /* current list */
533 int velocity; /* in pixel/s */
534} cb_data;
535
536/* data member points to the above struct */
537static struct timeout kinetic_tmo;
538
539static bool is_kinetic_over(void)
540{
541 return !cb_data.velocity && (scroll_mode == SCROLL_KINETIC);
542}
543
544/*
545 * collect data about how fast the list is moved in order to compute
546 * the initial velocity from it later */
547static void kinetic_stats_collect(const int difference)
548{
549 static long last_tick;
550 /* collect velocity statistics */
551 kinetic_data[cur_idx].difference = difference;
552 kinetic_data[cur_idx].ticks = current_tick - last_tick;
553
554 last_tick = current_tick;
555 cur_idx += 1;
556 if (cur_idx >= ARRAYLEN(kinetic_data))
557 cur_idx = 0; /* rewind the index */
558}
559
560/*
561 * resets the statistic */
562static void kinetic_stats_reset(void)
563{
564 memset(kinetic_data, 0, sizeof(kinetic_data));
565 cur_idx = 0;
566}
567
568/* cancels all currently active kinetic scrolling */
569static void kinetic_force_stop(void)
570{
571 timeout_cancel(&kinetic_tmo);
572 kinetic_stats_reset();
573}
574
575/* helper for gui/list.c to cancel scrolling if a normal button event comes
576 * through dpad or keyboard or whatever */
577void _gui_synclist_stop_kinetic_scrolling(struct gui_synclist * gui_list)
578{
579 const enum screen_type screen = screens[SCREEN_MAIN].screen_type;
580 gui_list->y_pos = gui_list->start_item[screen] * gui_list->line_height[screen];
581
582 if (scroll_mode == SCROLL_KINETIC)
583 kinetic_force_stop();
584 scroll_mode = SCROLL_NONE;
585 hide_selection = false;
586}
587/*
588 * returns false if scrolling should be stopped entirely
589 *
590 * otherwise it returns true even if it didn't actually scroll,
591 * but scrolling mode shouldn't be changed
592 **/
593
594
595static int scroll_begin_threshold;
596static int threshold_accumulation;
597static bool swipe_scroll(struct gui_synclist * gui_list, int difference)
598{
599 /* fixme */
600 const enum screen_type screen = screens[SCREEN_MAIN].screen_type;
601 const int nb_lines = list_get_nb_lines(gui_list, screen);
602 const int line_height = gui_list->line_height[screen];
603
604 if (UNLIKELY(scroll_begin_threshold == 0))
605 scroll_begin_threshold = touchscreen_get_scroll_threshold();
606
607 /* make selecting items easier */
608 threshold_accumulation += abs(difference);
609 if (threshold_accumulation < scroll_begin_threshold && scroll_mode == SCROLL_NONE)
610 return false;
611
612 threshold_accumulation = 0;
613
614 /* does the list even scroll? if no, return but still show
615 * the caller that we would scroll */
616 if (nb_lines >= gui_list->nb_items)
617 return true;
618
619 const int old_start = gui_list->start_item[screen];
620 int new_start_item = -1;
621 int line_diff = 0;
622 int max_y_pos = gui_list->nb_items * line_height - list_text[screen].height;
623
624 /* Track whether we hit the end of the list for sake of kinetic scroll */
625 bool hit_end = true;
626
627 /* Move the y position and clamp it (funny things happen otherwise...) */
628 gui_list->y_pos -= difference;
629 if(gui_list->y_pos < 0)
630 gui_list->y_pos = 0;
631 else if(gui_list->y_pos > max_y_pos)
632 gui_list->y_pos = max_y_pos;
633 else
634 hit_end = false;
635
636 /* Get the list y position. When pos_y differs by a line height or more,
637 * we need to scroll the list by adjusting the start item accordingly */
638 int cur_y = gui_list->start_item[screen] * line_height;
639 int diff_y = cur_y - gui_list->y_pos;
640 if (abs(diff_y) >= line_height)
641 {
642 line_diff = diff_y/line_height;
643 }
644
645 if(line_diff != 0)
646 {
647 int selection_offset = gui_list->selected_item - old_start;
648 new_start_item = old_start - line_diff;
649 /* check if new_start_item is bigger than list item count */
650 if(new_start_item > gui_list->nb_items - nb_lines)
651 new_start_item = gui_list->nb_items - nb_lines;
652 /* set new_start_item to 0 if it's negative */
653 if(new_start_item < 0)
654 new_start_item = 0;
655
656 gui_list->start_item[screen] = new_start_item;
657 /* keep selected item in sync */
658 gui_list->selected_item = new_start_item + selection_offset;
659 if(gui_list->selected_size > 1)
660 gui_list->selected_item -= (gui_list->selected_item % gui_list->selected_size);
661 }
662
663 if(hit_end)
664 return scroll_mode != SCROLL_KINETIC;
665 else
666 return true;
667}
668
669static int kinetic_callback(struct timeout *tmo)
670{
671 /* cancel if screen was pressed */
672 if (scroll_mode != SCROLL_KINETIC)
673 return 0;
674
675 struct cb_data *data = (struct cb_data*)tmo->data;
676 /* ds = v*dt */
677 int pixel_diff = data->velocity * RELOAD_INTERVAL / HZ;
678 /* remember signedness to detect stopping */
679 int old_sign = SIGN(data->velocity);
680 /* advance the list */
681 if (!swipe_scroll(data->list, pixel_diff))
682 {
683 /* nothing to scroll? */
684 data->velocity = 0;
685 }
686 else
687 {
688 /* decelerate by a fixed amount
689 * decrementing v0 over time by the deceleration is
690 * equivalent to computing v = a*t + v0 */
691 data->velocity -= SIGN(data->velocity)*DECELERATION;
692 if (SIGN(data->velocity) != old_sign)
693 data->velocity = 0;
694 }
695
696 /* let get_action() timeout, which loads to a
697 * gui_synclist_draw() call from the main thread */
698 button_queue_post(BUTTON_REDRAW, 0);
699 /* stop if the velocity hit or crossed zero */
700 if (!data->velocity)
701 {
702 kinetic_stats_reset();
703 return 0;
704 }
705 return RELOAD_INTERVAL; /* cancel or reload */
706}
707
708/*
709 * computes the initial velocity v0 and sets up the timer */
710static bool kinetic_setup_scroll(struct gui_synclist *list)
711{
712 /* compute initial velocity */
713 int i, _i, v0, len = ARRAYLEN(kinetic_data);
714 for(i = 0, _i = 0, v0 = 0; i < len; i++)
715 { /* in pixel/s */
716 if (kinetic_data[i].ticks > 0)
717 {
718 v0 += kinetic_data[i].difference*HZ/kinetic_data[i].ticks;
719 _i++;
720 }
721 }
722 if (_i > 0)
723 v0 /= _i;
724 else
725 v0 = 0;
726
727 if (v0 != 0)
728 {
729 cb_data.list = list;
730 cb_data.velocity = v0;
731 timeout_register(&kinetic_tmo, kinetic_callback, RELOAD_INTERVAL, (intptr_t)&cb_data);
732 return true;
733 }
734 return false;
735}
736
737#define OUTSIDE 0
738#define TITLE_TEXT (1<<0)
739#define TITLE_ICON (1<<1)
740#define SCROLLBAR (1<<2)
741#define LIST_TEXT (1<<3)
742#define LIST_ICON (1<<4)
743
744#define TITLE (TITLE_TEXT|TITLE_ICON)
745#define LIST (LIST_TEXT|LIST_ICON)
746
747static int get_click_location(struct gui_synclist *list, int x, int y)
748{
749 int screen = SCREEN_MAIN;
750 struct viewport *parent, *title, *text;
751 int retval = OUTSIDE;
752
753 parent = list->parent[screen];
754 if (viewport_point_within_vp(parent, x, y))
755 {
756 /* see if the title was clicked */
757 title = &title_text[screen];
758 if (viewport_point_within_vp(title, x, y))
759 retval = TITLE_TEXT;
760 /* check the icon too */
761 if (list->title_icon != Icon_NOICON && list->show_icons)
762 {
763 int width = list_icon_width(screen);
764 struct viewport vp = *title;
765 if (VP_IS_RTL(&vp))
766 vp.x += vp.width;
767 else
768 vp.x -= width;
769 vp.width = width;
770 if (viewport_point_within_vp(&vp, x, y))
771 retval = TITLE_ICON;
772 }
773 /* check scrollbar. assume it's shown, if it isn't it will be handled
774 * later */
775 if (retval == OUTSIDE)
776 {
777 bool on_scrollbar_clicked;
778 int adj_x = x - parent->x;
779 switch (list->scrollbar)
780 {
781 case SCROLLBAR_OFF:
782 /*fall-through*/
783 default:
784 on_scrollbar_clicked = false;
785 break;
786 case SCROLLBAR_LEFT:
787 on_scrollbar_clicked = adj_x <= SCROLLBAR_WIDTH;
788 break;
789 case SCROLLBAR_RIGHT:
790 on_scrollbar_clicked = adj_x > (title->x + title->width - SCROLLBAR_WIDTH);
791 break;
792 }
793 if (on_scrollbar_clicked)
794 retval = SCROLLBAR;
795 }
796 if (retval == OUTSIDE)
797 {
798 text = &list_text[screen];
799 if (viewport_point_within_vp(text, x, y))
800 retval = LIST_TEXT;
801 else /* if all fails, it must be on the list icons */
802 retval = LIST_ICON;
803 }
804 }
805 return retval;
806}
807
808unsigned gui_synclist_do_touchscreen(struct gui_synclist * list)
809{
810 enum screen_type screen;
811 struct viewport *parent;
812 short x, y;
813 int action, adj_x, adj_y, line, line_height, list_start_item;
814 bool recurse;
815 static bool initial_touch = true;
816 static int last_y;
817
818 screen = SCREEN_MAIN;
819 parent = list->parent[screen];
820 line_height = list->line_height[screen];
821 list_start_item = list->start_item[screen];
822 /* start with getting the action code and finding the click location */
823 action = action_get_touchscreen_press(&x, &y);
824 adj_x = x - parent->x;
825 adj_y = y - parent->y;
826
827
828 /* some defaults before running the state machine */
829 recurse = false;
830 hide_selection = false;
831
832 switch (scroll_mode)
833 {
834 case SCROLL_NONE:
835 {
836 int click_loc;
837 if (initial_touch)
838 {
839 /* on the first touch last_y has to be reset to avoid
840 * glitches with touches from long ago */
841 last_y = adj_y;
842 initial_touch = false;
843 }
844
845 line = 0; /* silence gcc 'used uninitialized' warning */
846 click_loc = get_click_location(list, x, y);
847 if (click_loc & LIST)
848 {
849 if(!skinlist_get_item(&screens[screen], list, adj_x, adj_y, &line))
850 {
851 /* selection needs to be corrected if items are only partially visible */
852 int cur_y = list->start_item[screen] * line_height;
853 line = (adj_y - (cur_y - list->y_pos)) / line_height;
854 if (list_display_title(list, screen))
855 line -= 1; /* adjust for the list title */
856 }
857 if (list_start_item+line >= list->nb_items)
858 return ACTION_NONE;
859 list->selected_item = list_start_item+line;
860 if(list->selected_size > 1)
861 list->selected_item -= (list->selected_item % list->selected_size);
862
863 gui_synclist_speak_item(list);
864 }
865 if (action == BUTTON_TOUCHSCREEN)
866 {
867 /* if not scrolling, the user is trying to select */
868 int diff = adj_y - last_y;
869 if ((click_loc & LIST) && swipe_scroll(list, diff))
870 scroll_mode = SCROLL_SWIPE;
871 else if (click_loc & SCROLLBAR)
872 scroll_mode = SCROLL_BAR;
873 }
874 else if (action == BUTTON_REPEAT)
875 {
876 if (click_loc & LIST)
877 {
878 /* held a single line for a while, bring up the context menu */
879 gui_synclist_select_item(list, list->selected_item);
880 /* don't sent context repeatedly */
881 action_wait_for_release();
882 initial_touch = true;
883 return ACTION_STD_CONTEXT;
884 }
885 }
886 else if (action & BUTTON_REL)
887 {
888 initial_touch = true;
889 if (click_loc & LIST)
890 { /* release on list item enters it */
891 gui_synclist_select_item(list, list->selected_item);
892 return ACTION_STD_OK;
893 }
894 else if (click_loc & TITLE_TEXT)
895 { /* clicking the title goes one level up (cancel) */
896 return ACTION_STD_CANCEL;
897 }
898 else if (click_loc & TITLE_ICON)
899 { /* clicking the title icon goes back to the root */
900 return ACTION_STD_MENU;
901 }
902 }
903 break;
904 }
905 case SCROLL_SWIPE:
906 {
907 /* when swipe scrolling, we accept outside presses as well and
908 * grab the entire screen (i.e. click_loc does not matter) */
909 int diff = adj_y - last_y;
910 hide_selection = true;
911 kinetic_stats_collect(diff);
912 if (swipe_scroll(list, diff))
913 {
914 /* letting the pen go enters kinetic scrolling */
915 if ((action & BUTTON_REL))
916 {
917 if (kinetic_setup_scroll(list))
918 {
919 hide_selection = true;
920 scroll_mode = SCROLL_KINETIC;
921 }
922 else
923 scroll_mode = SCROLL_NONE;
924 }
925 }
926 else if (action & BUTTON_REL)
927 scroll_mode = SCROLL_NONE;
928
929 if (scroll_mode == SCROLL_NONE)
930 initial_touch = true;
931 break;
932 }
933 case SCROLL_KINETIC:
934 {
935 /* during kinetic scrolling we need to handle cancellation.
936 * This state is actually only entered upon end of it as this
937 * function is not called during the animation. */
938 if (!is_kinetic_over())
939 { /* a) the user touched the screen (manual cancellation) */
940 kinetic_force_stop();
941 if (get_click_location(list, x, y) & SCROLLBAR)
942 scroll_mode = SCROLL_BAR;
943 else
944 scroll_mode = SCROLL_SWIPE;
945 }
946 else
947 { /* b) kinetic scrolling stopped on its own */
948 /* need to re-run this with SCROLL_NONE since otherwise
949 * the next touch is not detected correctly */
950 scroll_mode = SCROLL_NONE;
951 recurse = true;
952 }
953 break;
954 }
955 case SCROLL_BAR:
956 {
957 hide_selection = true;
958 /* similarly to swipe scroll, using the scrollbar grabs
959 * focus so the click location is irrelevant */
960 scrollbar_scroll(list, y);
961 if (action & BUTTON_REL)
962 scroll_mode = SCROLL_NONE;
963 break;
964 }
965 }
966
967 /* register y position unless forcefully reset */
968 if (!initial_touch)
969 last_y = adj_y;
970
971 return recurse ? gui_synclist_do_touchscreen(list) : ACTION_REDRAW;
972}
973
974#endif