summaryrefslogtreecommitdiff
path: root/apps/gui/bitmap/list.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/gui/bitmap/list.c')
-rw-r--r--apps/gui/bitmap/list.c299
1 files changed, 249 insertions, 50 deletions
diff --git a/apps/gui/bitmap/list.c b/apps/gui/bitmap/list.c
index ec12ee5367..d78f005e6a 100644
--- a/apps/gui/bitmap/list.c
+++ b/apps/gui/bitmap/list.c
@@ -52,9 +52,10 @@ static struct viewport list_text[NB_SCREENS], title_text[NB_SCREENS];
52#define SCROLL_BEGIN_THRESHOLD 3 52#define SCROLL_BEGIN_THRESHOLD 3
53 53
54static enum { 54static enum {
55 SCROLL_NONE, /* no scrolling */ 55 SCROLL_NONE, /* no scrolling */
56 SCROLL_BAR, /* scroll by using the scrollbar */ 56 SCROLL_BAR, /* scroll by using the scrollbar */
57 SCROLL_SWIPE, /* scroll by wiping over the screen */ 57 SCROLL_SWIPE, /* scroll by wiping over the screen */
58 SCROLL_KINETIC, /* state after releasing swipe */
58} scroll_mode; 59} scroll_mode;
59 60
60static int y_offset; 61static int y_offset;
@@ -162,12 +163,13 @@ void list_draw(struct screen *display, struct gui_synclist *list)
162 list_text_vp->height -= line_height; 163 list_text_vp->height -= line_height;
163 } 164 }
164 165
166 const int nb_lines = viewport_get_nb_lines(list_text_vp);
165 167
166 start = list_start_item; 168 start = list_start_item;
167 end = start + viewport_get_nb_lines(list_text_vp); 169 end = start + nb_lines;
168 170
169#ifdef HAVE_TOUCHSCREEN 171#ifdef HAVE_TOUCHSCREEN
170 if (list->selected_item == 0) 172 if (list->selected_item == 0 || (list->nb_items < nb_lines))
171 y_offset = 0; /* reset in case it's a new list */ 173 y_offset = 0; /* reset in case it's a new list */
172 174
173 int draw_offset = y_offset; 175 int draw_offset = y_offset;
@@ -187,12 +189,11 @@ void list_draw(struct screen *display, struct gui_synclist *list)
187#endif 189#endif
188 190
189 /* draw the scrollbar if its needed */ 191 /* draw the scrollbar if its needed */
190 if (global_settings.scrollbar && 192 if (global_settings.scrollbar && nb_lines < list->nb_items)
191 viewport_get_nb_lines(list_text_vp) < list->nb_items)
192 { 193 {
193 struct viewport vp = *list_text_vp; 194 struct viewport vp = *list_text_vp;
194 vp.width = SCROLLBAR_WIDTH; 195 vp.width = SCROLLBAR_WIDTH;
195 vp.height = line_height * viewport_get_nb_lines(list_text_vp); 196 vp.height = line_height * nb_lines;
196 vp.x = parent->x; 197 vp.x = parent->x;
197 list_text_vp->width -= SCROLLBAR_WIDTH; 198 list_text_vp->width -= SCROLLBAR_WIDTH;
198 if (scrollbar_in_left) 199 if (scrollbar_in_left)
@@ -202,7 +203,7 @@ void list_draw(struct screen *display, struct gui_synclist *list)
202 display->set_viewport(&vp); 203 display->set_viewport(&vp);
203 gui_scrollbar_draw(display, 204 gui_scrollbar_draw(display,
204 (scrollbar_in_left? 0: 1), 0, SCROLLBAR_WIDTH-1, vp.height, 205 (scrollbar_in_left? 0: 1), 0, SCROLLBAR_WIDTH-1, vp.height,
205 list->nb_items, list_start_item, list_start_item + end-start, 206 list->nb_items, list_start_item, list_start_item + nb_lines,
206 VERTICAL); 207 VERTICAL);
207 } 208 }
208 else if (show_title) 209 else if (show_title)
@@ -359,8 +360,7 @@ static bool released = false;
359 */ 360 */
360static int last_position=0; 361static int last_position=0;
361 362
362 363static int scrollbar_scroll(struct gui_synclist * gui_list,
363static int gui_synclist_touchscreen_scrollbar(struct gui_synclist * gui_list,
364 int y) 364 int y)
365{ 365{
366 const int screen = screens[SCREEN_MAIN].screen_type; 366 const int screen = screens[SCREEN_MAIN].screen_type;
@@ -391,25 +391,115 @@ static int gui_synclist_touchscreen_scrollbar(struct gui_synclist * gui_list,
391 return ACTION_NONE; 391 return ACTION_NONE;
392} 392}
393 393
394/* kinetic scrolling, based on
395 *
396 * v = a*t + v0 and ds = v*dt
397 *
398 * In each (fixed interval) timeout, the list is advanced by ds, then
399 * the v is reduced by a.
400 * This way we get a linear and smooth deceleration of the scrolling
401 *
402 * As v is the difference of distance per time unit, v is passed (as
403 * pixels moved since the last call) to the scrolling function which takes
404 * care of the pixel accurate drawing
405 *
406 * v0 is dertermined by averaging the last 4 movements of the list
407 * (the pixel and time difference is used to compute each v)
408 *
409 * influenced by http://stechz.com/tag/kinetic/
410 * We take the easy and smooth first approach (until section "Drawbacks"),
411 * since its drawbacks don't apply for us since our timers seem to be
412 * relatively accurate
413 */
414
415
416#define SIGN(a) ((a) < 0 ? -1 : 1)
417/* these could possibly be configurable */
418/* the lower the smoother */
419#define RELOAD_INTERVAL (HZ/25)
420/* the higher the earler the list stops */
421#define DECELERATION (1000*RELOAD_INTERVAL/HZ)
422
423/* this array holds data to compute the initial velocity v0 */
424static struct kinetic_info {
425 int difference;
426 long ticks;
427} kinetic_data[4];
428static size_t cur_idx;
429
430static struct cb_data {
431 struct gui_synclist *list; /* current list */
432 int velocity; /* in pixel/s */
433} cb_data;
434
435/* data member points to the above struct */
436static struct timeout kinetic_tmo;
437
438static bool is_kinetic_over(void)
439{
440 return !cb_data.velocity && (scroll_mode == SCROLL_KINETIC);
441}
442
443/*
444 * collect data about how fast the list is moved in order to compute
445 * the initial velocity from it later */
446static void kinetic_stats_collect(const int difference)
447{
448 static long last_tick;
449 /* collect velocity statistics */
450 kinetic_data[cur_idx].difference = difference;
451 kinetic_data[cur_idx].ticks = current_tick - last_tick;
452
453 last_tick = current_tick;
454 cur_idx += 1;
455 if (cur_idx >= ARRAYLEN(kinetic_data))
456 cur_idx = 0; /* rewind the index */
457}
458
394/* 459/*
395 * returns the number of pixel scrolled since the last call 460 * resets the statistic */
461static void kinetic_stats_reset(void)
462{
463 memset(kinetic_data, 0, sizeof(kinetic_data));
464 cur_idx = 0;
465}
466
467/* cancels all currently active kinetic scrolling */
468static void kinetic_force_stop(void)
469{
470 timeout_cancel(&kinetic_tmo);
471 kinetic_stats_reset();
472}
473
474/* helper for gui/list.c to cancel scrolling if a normal button event comes
475 * through dpad or keyboard or whatever */
476void _gui_synclist_stop_kinetic_scrolling(void)
477{
478 y_offset = 0;
479 if (scroll_mode == SCROLL_KINETIC)
480 kinetic_force_stop();
481 scroll_mode = SCROLL_NONE;
482}
483/*
484 * returns false if scrolling should be stopped entirely
485 *
486 * otherwise it returns true even if it didn't actually scroll,
487 * but scrolling mode shouldn't be changed
396 **/ 488 **/
397static int gui_synclist_touchscreen_scrolling(struct gui_synclist * gui_list, int line_height, int position) 489static bool swipe_scroll(struct gui_synclist * gui_list, int line_height, int difference)
398{ 490{
399 /* fixme */ 491 /* fixme */
400 const enum screen_type screen = screens[SCREEN_MAIN].screen_type; 492 const enum screen_type screen = screens[SCREEN_MAIN].screen_type;
401 const int nb_lines = viewport_get_nb_lines(&list_text[screen]); 493 const int nb_lines = viewport_get_nb_lines(&list_text[screen]);
402 /* in pixels */
403 const int difference = position - last_position;
404 494
405 /* make selecting items easier */ 495 /* make selecting items easier */
406 if (abs(difference) < SCROLL_BEGIN_THRESHOLD && scroll_mode == SCROLL_NONE) 496 if (abs(difference) < SCROLL_BEGIN_THRESHOLD && scroll_mode == SCROLL_NONE)
407 return 0; 497 return false;
408 498
409 /* does the list even scroll? if no, return but still show 499 /* does the list even scroll? if no, return but still show
410 * the caller that we would scroll */ 500 * the caller that we would scroll */
411 if (nb_lines >= gui_list->nb_items) 501 if (nb_lines >= gui_list->nb_items)
412 return difference; 502 return true;
413 503
414 const int old_start = gui_list->start_item[screen]; 504 const int old_start = gui_list->start_item[screen];
415 int new_start_item = -1; 505 int new_start_item = -1;
@@ -420,7 +510,8 @@ static int gui_synclist_touchscreen_scrolling(struct gui_synclist * gui_list, in
420 || (old_start == (gui_list->nb_items - nb_lines) && difference < 0)) 510 || (old_start == (gui_list->nb_items - nb_lines) && difference < 0))
421 { 511 {
422 y_offset = 0; 512 y_offset = 0;
423 return difference; 513 gui_list->start_item[screen] = old_start;
514 return scroll_mode != SCROLL_KINETIC; /* stop kinetic at the edges */
424 } 515 }
425 516
426 /* add up y_offset over time and translate to lines 517 /* add up y_offset over time and translate to lines
@@ -434,6 +525,7 @@ static int gui_synclist_touchscreen_scrolling(struct gui_synclist * gui_list, in
434 525
435 if(line_diff != 0) 526 if(line_diff != 0)
436 { 527 {
528 int selection_offset = gui_list->selected_item - old_start;
437 new_start_item = old_start - line_diff; 529 new_start_item = old_start - line_diff;
438 /* check if new_start_item is bigger than list item count */ 530 /* check if new_start_item is bigger than list item count */
439 if(new_start_item > gui_list->nb_items - nb_lines) 531 if(new_start_item > gui_list->nb_items - nb_lines)
@@ -441,10 +533,82 @@ static int gui_synclist_touchscreen_scrolling(struct gui_synclist * gui_list, in
441 /* set new_start_item to 0 if it's negative */ 533 /* set new_start_item to 0 if it's negative */
442 if(new_start_item < 0) 534 if(new_start_item < 0)
443 new_start_item = 0; 535 new_start_item = 0;
536
444 gui_list->start_item[screen] = new_start_item; 537 gui_list->start_item[screen] = new_start_item;
538 /* keep selected item in sync */
539 gui_list->selected_item = new_start_item + selection_offset;
540 }
541
542 return true;
543}
544
545static int kinetic_callback(struct timeout *tmo)
546{
547 /* cancel if screen was pressed */
548 if (scroll_mode != SCROLL_KINETIC)
549 return 0;
550
551 struct cb_data *data = (struct cb_data*)tmo->data;
552 int line_height = font_get(data->list->parent[0]->font)->height;
553 /* ds = v*dt */
554 int pixel_diff = data->velocity * RELOAD_INTERVAL / HZ;
555 /* remember signedness to detect stopping */
556 int old_sign = SIGN(data->velocity);
557 /* advance the list */
558 if (!swipe_scroll(data->list, line_height, pixel_diff))
559 {
560 /* nothing to scroll? */
561 data->velocity = 0;
562 }
563 else
564 {
565 /* decelerate by a fixed amount
566 * decrementing v0 over time by the deceleration is
567 * equivalent to computing v = a*t + v0 */
568 data->velocity -= SIGN(data->velocity)*DECELERATION;
569 if (SIGN(data->velocity) != old_sign)
570 data->velocity = 0;
571 }
572
573 queue_post(&button_queue, BUTTON_TOUCHSCREEN, 0);
574 /* stop if the velocity hit or crossed zero */
575 if (!data->velocity)
576 {
577 kinetic_stats_reset();
578 return 0;
579 }
580 /* let get_action() timeout, which loads to a
581 * gui_synclist_draw() call from the main thread */
582 return RELOAD_INTERVAL; /* cancel or reload */
583}
584
585/*
586 * computes the initial velocity v0 and sets up the timer */
587static bool kinetic_setup_scroll(struct gui_synclist *list)
588{
589 /* compute initial velocity */
590 int i, _i, v0, len = ARRAYLEN(kinetic_data);
591 for(i = 0, _i = 0, v0 = 0; i < len; i++)
592 { /* in pixel/s */
593 if (kinetic_data[i].ticks > 0)
594 {
595 v0 += kinetic_data[i].difference*HZ/kinetic_data[i].ticks;
596 _i++;
597 }
445 } 598 }
599 if (_i > 0)
600 v0 /= _i;
601 else
602 v0 = 0;
446 603
447 return difference; 604 if (v0 != 0)
605 {
606 cb_data.list = list;
607 cb_data.velocity = v0;
608 timeout_register(&kinetic_tmo, kinetic_callback, RELOAD_INTERVAL, (intptr_t)&cb_data);
609 return true;
610 }
611 return false;
448} 612}
449 613
450unsigned gui_synclist_do_touchscreen(struct gui_synclist * gui_list) 614unsigned gui_synclist_do_touchscreen(struct gui_synclist * gui_list)
@@ -460,12 +624,23 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist * gui_list)
460 const bool show_title = list_display_title(gui_list, screen); 624 const bool show_title = list_display_title(gui_list, screen);
461 const bool show_cursor = !global_settings.cursor_style && 625 const bool show_cursor = !global_settings.cursor_style &&
462 gui_list->show_selection_marker; 626 gui_list->show_selection_marker;
463 const bool on_title_clicked = show_title && y < line_height; 627 const bool on_title_clicked = show_title && y < line_height && (button&BUTTON_REL);
628 const bool cancelled_kinetic = (scroll_mode == SCROLL_KINETIC
629 && button != ACTION_NONE && button != ACTION_UNKNOWN
630 && !is_kinetic_over());
464 int icon_width = 0; 631 int icon_width = 0;
465 int line, list_width = list_text_vp->width; 632 int line, list_width = list_text_vp->width;
466 633
634 released = (button&BUTTON_REL) != 0;
635
467 if (button == ACTION_NONE || button == ACTION_UNKNOWN) 636 if (button == ACTION_NONE || button == ACTION_UNKNOWN)
637 {
638 /* this happens when we hit edges of the list while kinetic scrolling,
639 * but not when manually cancelling */
640 if (scroll_mode == SCROLL_KINETIC)
641 return ACTION_REDRAW;
468 return ACTION_NONE; 642 return ACTION_NONE;
643 }
469 644
470 /* x and y are relative to info_vp */ 645 /* x and y are relative to info_vp */
471 if (gui_list->callback_get_item_icon != NULL) 646 if (gui_list->callback_get_item_icon != NULL)
@@ -473,27 +648,32 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist * gui_list)
473 if (show_cursor) 648 if (show_cursor)
474 icon_width += get_icon_width(screen); 649 icon_width += get_icon_width(screen);
475 650
476 released = (button&BUTTON_REL) != 0;
477
478 if (button == BUTTON_NONE)
479 return ACTION_NONE;
480
481 if (on_title_clicked) 651 if (on_title_clicked)
482 { 652 {
483 if (x < icon_width) 653 if (scroll_mode == SCROLL_NONE || is_kinetic_over())
654 {
655 if (x < icon_width)
656 {
657 /* Top left corner is GO_TO_ROOT */
658 if (button == BUTTON_REL)
659 return ACTION_STD_MENU;
660 else if (button == (BUTTON_REPEAT|BUTTON_REL))
661 return ACTION_STD_CONTEXT;
662 return ACTION_NONE;
663 }
664 else /* click on title text is cancel */
665 if (button == BUTTON_REL)
666 return ACTION_STD_CANCEL;
667 }
668 /* do this after the above so the scrolling stops without
669 * going back in the list with the same touch */
670 if (scroll_mode == SCROLL_KINETIC)
484 { 671 {
485 /* Top left corner is GO_TO_ROOT */ 672 kinetic_force_stop();
486 if (button == BUTTON_REL) 673 scroll_mode = SCROLL_NONE;
487 return ACTION_STD_MENU;
488 else if (button == (BUTTON_REPEAT|BUTTON_REL))
489 return ACTION_STD_CONTEXT;
490 return ACTION_NONE;
491 } 674 }
492 else /* click on title text is cancel */
493 if (button == BUTTON_REL && scroll_mode == SCROLL_NONE)
494 return ACTION_STD_CANCEL;
495 } 675 }
496 else /* list area clicked */ 676 else /* list area clicked (or not released) */
497 { 677 {
498 const int actual_y = y - (show_title ? line_height : 0); 678 const int actual_y = y - (show_title ? line_height : 0);
499 bool on_scrollbar_clicked; 679 bool on_scrollbar_clicked;
@@ -516,11 +696,13 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist * gui_list)
516 * via swiping the screen 696 * via swiping the screen
517 **/ 697 **/
518 698
519 if (!released && scroll_mode < SCROLL_SWIPE && 699 if (!released && scroll_mode != SCROLL_SWIPE &&
520 (on_scrollbar_clicked || scroll_mode == SCROLL_BAR)) 700 (on_scrollbar_clicked || scroll_mode == SCROLL_BAR))
521 { 701 {
702 if (scroll_mode == SCROLL_KINETIC)
703 kinetic_force_stop();
522 scroll_mode = SCROLL_BAR; 704 scroll_mode = SCROLL_BAR;
523 return gui_synclist_touchscreen_scrollbar(gui_list, y); 705 return scrollbar_scroll(gui_list, y);
524 } 706 }
525 707
526 /* |--------------------------------------------------------| 708 /* |--------------------------------------------------------|
@@ -540,7 +722,13 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist * gui_list)
540 /* selection needs to be corrected if an items are only 722 /* selection needs to be corrected if an items are only
541 * partially visible */ 723 * partially visible */
542 line = (actual_y - y_offset) / line_height; 724 line = (actual_y - y_offset) / line_height;
543 725
726 if (cancelled_kinetic)
727 {
728 kinetic_force_stop();
729 scroll_mode = SCROLL_SWIPE;
730 }
731
544 /* Pressed below the list*/ 732 /* Pressed below the list*/
545 if (list_start_item + line >= gui_list->nb_items) 733 if (list_start_item + line >= gui_list->nb_items)
546 { 734 {
@@ -550,7 +738,7 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist * gui_list)
550 return ACTION_NONE; 738 return ACTION_NONE;
551 } 739 }
552 740
553 if (released) 741 if (released && !cancelled_kinetic)
554 { 742 {
555 /* Pen was released anywhere on the screen */ 743 /* Pen was released anywhere on the screen */
556 last_position = 0; 744 last_position = 0;
@@ -571,29 +759,41 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist * gui_list)
571 { 759 {
572 /* we were scrolling 760 /* we were scrolling
573 * -> reset scrolling but do nothing else */ 761 * -> reset scrolling but do nothing else */
574 scroll_mode = SCROLL_NONE; 762 if (scroll_mode == SCROLL_SWIPE)
763 {
764 if (kinetic_setup_scroll(gui_list))
765 scroll_mode = SCROLL_KINETIC;
766 }
767 if (scroll_mode != SCROLL_KINETIC)
768 scroll_mode = SCROLL_NONE;
575 return ACTION_NONE; 769 return ACTION_NONE;
576 } 770 }
577 } 771 }
578 else 772 else
579 { /* pen is on the screen */ 773 { /* pen is on the screen */
580 int result = 0; 774 bool redraw = false, result = false;
581 bool redraw = false;
582
583 /* beginning of list interaction denoted by release in 775 /* beginning of list interaction denoted by release in
584 * the previous call */ 776 * the previous call */
585 if (old_released) 777 if (old_released || is_kinetic_over())
586 { 778 {
587 scroll_mode = SCROLL_NONE; 779 scroll_mode = SCROLL_NONE;
588 redraw = true; 780 redraw = true;
589 } 781 }
590 782
591 /* select current item */ 783 /* select current item; gui_synclist_select_item()
592 gui_synclist_select_item(gui_list, list_start_item+line); 784 * is not called because it has side effects that
785 * disturb kinetic scrolling */
786 gui_list->selected_item = list_start_item+line;
787 gui_synclist_speak_item(gui_list);
593 if (last_position == 0) 788 if (last_position == 0)
594 last_position = actual_y; 789 last_position = actual_y;
595 else 790 else
596 result = gui_synclist_touchscreen_scrolling(gui_list, line_height, actual_y); 791 {
792 /* record speed data in case we do kinetic scrolling */
793 int diff = actual_y - last_position;
794 kinetic_stats_collect(diff);
795 result = swipe_scroll(gui_list, line_height, diff);
796 }
597 797
598 /* Start scrolling once the pen is moved without 798 /* Start scrolling once the pen is moved without
599 * releasing it inbetween */ 799 * releasing it inbetween */
@@ -602,13 +802,12 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist * gui_list)
602 redraw = true; 802 redraw = true;
603 scroll_mode = SCROLL_SWIPE; 803 scroll_mode = SCROLL_SWIPE;
604 } 804 }
605
606 last_position = actual_y; 805 last_position = actual_y;
607 806
608 return redraw ? ACTION_REDRAW:ACTION_NONE; 807 return redraw ? ACTION_REDRAW:ACTION_NONE;
609 } 808 }
610 } 809 }
611 } 810 }
612 return ACTION_NONE; 811 return ACTION_REDRAW;
613} 812}
614#endif 813#endif