touchscreen: fix smooth scrolling in lists

This fixes those annoying jumps that happen when you hit the end of
a list while scrolling.

Change-Id: I2e41111f9415dce1692b52a2600e7ce77c8f0291
This commit is contained in:
Aidan MacDonald 2021-08-22 01:46:28 +01:00
parent 569b165cff
commit d1653bc4d8
3 changed files with 47 additions and 28 deletions

View file

@ -50,7 +50,6 @@
static struct viewport list_text[NB_SCREENS], title_text[NB_SCREENS]; static struct viewport list_text[NB_SCREENS], title_text[NB_SCREENS];
#ifdef HAVE_TOUCHSCREEN #ifdef HAVE_TOUCHSCREEN
static int y_offset;
static bool hide_selection; static bool hide_selection;
#endif #endif
@ -170,12 +169,9 @@ void list_draw(struct screen *display, struct gui_synclist *list)
end = start + nb_lines; end = start + nb_lines;
#ifdef HAVE_TOUCHSCREEN #ifdef HAVE_TOUCHSCREEN
if (list->selected_item == 0 || (list->nb_items < nb_lines)) int draw_offset = list_start_item * linedes.height - list->y_pos;
y_offset = 0; /* reset in case it's a new list */
int draw_offset = y_offset;
/* draw some extra items to not have empty lines at the top and bottom */ /* draw some extra items to not have empty lines at the top and bottom */
if (y_offset > 0) if (draw_offset > 0)
{ {
/* make it negative for more consistent apparence when switching /* make it negative for more consistent apparence when switching
* directions */ * directions */
@ -183,7 +179,7 @@ void list_draw(struct screen *display, struct gui_synclist *list)
if (start > 0) if (start > 0)
start--; start--;
} }
else if (y_offset < 0) else if (draw_offset < 0)
end++; end++;
#else #else
#define draw_offset 0 #define draw_offset 0
@ -367,7 +363,6 @@ static int scrollbar_scroll(struct gui_synclist * gui_list, int y)
if (nb_lines < gui_list->nb_items) if (nb_lines < gui_list->nb_items)
{ {
/* scrollbar scrolling is still line based */ /* scrollbar scrolling is still line based */
y_offset = 0;
int scrollbar_size = nb_lines * gui_list->line_height[screen]; int scrollbar_size = nb_lines * gui_list->line_height[screen];
int actual_y = y - list_text[screen].y; int actual_y = y - list_text[screen].y;
int new_selection = (actual_y * gui_list->nb_items) / scrollbar_size; int new_selection = (actual_y * gui_list->nb_items) / scrollbar_size;
@ -379,6 +374,7 @@ static int scrollbar_scroll(struct gui_synclist * gui_list, int y)
start_item = gui_list->nb_items - nb_lines; start_item = gui_list->nb_items - nb_lines;
gui_list->start_item[screen] = start_item; gui_list->start_item[screen] = start_item;
gui_list->y_pos = start_item * gui_list->line_height[screen];
return ACTION_REDRAW; return ACTION_REDRAW;
} }
@ -468,9 +464,11 @@ static void kinetic_force_stop(void)
/* helper for gui/list.c to cancel scrolling if a normal button event comes /* helper for gui/list.c to cancel scrolling if a normal button event comes
* through dpad or keyboard or whatever */ * through dpad or keyboard or whatever */
void _gui_synclist_stop_kinetic_scrolling(void) void _gui_synclist_stop_kinetic_scrolling(struct gui_synclist * gui_list)
{ {
y_offset = 0; const enum screen_type screen = screens[SCREEN_MAIN].screen_type;
gui_list->y_pos = gui_list->start_item[screen] * gui_list->line_height[screen];
if (scroll_mode == SCROLL_KINETIC) if (scroll_mode == SCROLL_KINETIC)
kinetic_force_stop(); kinetic_force_stop();
scroll_mode = SCROLL_NONE; scroll_mode = SCROLL_NONE;
@ -512,22 +510,25 @@ static bool swipe_scroll(struct gui_synclist * gui_list, int difference)
int new_start_item = -1; int new_start_item = -1;
int line_diff = 0; int line_diff = 0;
/* don't scroll at the edges of the list */ /* Track whether we hit the end of the list for sake of kinetic scroll */
if ((old_start == 0 && difference > 0) bool hit_end = true;
|| (old_start == (gui_list->nb_items - nb_lines) && difference < 0))
{
y_offset = 0;
gui_list->start_item[screen] = old_start;
return scroll_mode != SCROLL_KINETIC; /* stop kinetic at the edges */
}
/* add up y_offset over time and translate to lines /* Move the y position and clamp it (funny things happen otherwise...) */
* if scrolled enough */ gui_list->y_pos -= difference;
y_offset += difference; if(gui_list->y_pos < 0)
if (abs(y_offset) > line_height) gui_list->y_pos = 0;
else if(gui_list->y_pos > (gui_list->nb_items - nb_lines) * line_height)
gui_list->y_pos = (gui_list->nb_items - nb_lines) * line_height;
else
hit_end = false;
/* Get the list y position. When pos_y differs by a line height or more,
* we need to scroll the list by adjusting the start item accordingly */
int cur_y = gui_list->start_item[screen] * line_height;
int diff_y = cur_y - gui_list->y_pos;
if (abs(diff_y) >= line_height)
{ {
line_diff = y_offset/line_height; line_diff = diff_y/line_height;
y_offset -= line_diff * line_height;
} }
if(line_diff != 0) if(line_diff != 0)
@ -548,7 +549,10 @@ static bool swipe_scroll(struct gui_synclist * gui_list, int difference)
gui_list->selected_item -= (gui_list->selected_item % gui_list->selected_size); gui_list->selected_item -= (gui_list->selected_item % gui_list->selected_size);
} }
return true; if(hit_end)
return scroll_mode != SCROLL_KINETIC;
else
return true;
} }
static int kinetic_callback(struct timeout *tmo) static int kinetic_callback(struct timeout *tmo)
@ -729,7 +733,8 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist * list)
if(!skinlist_get_item(&screens[screen], list, adj_x, adj_y, &line)) if(!skinlist_get_item(&screens[screen], list, adj_x, adj_y, &line))
{ {
/* selection needs to be corrected if items are only partially visible */ /* selection needs to be corrected if items are only partially visible */
line = (adj_y - y_offset) / line_height; int cur_y = list->start_item[screen] * line_height;
line = (adj_y - (cur_y - list->y_pos)) / line_height;
if (list_display_title(list, screen)) if (list_display_title(list, screen))
line -= 1; /* adjust for the list title */ line -= 1; /* adjust for the list title */
} }

View file

@ -152,6 +152,9 @@ void gui_synclist_init(struct gui_synclist * gui_list,
gui_list->callback_speak_item = NULL; gui_list->callback_speak_item = NULL;
gui_list->nb_items = 0; gui_list->nb_items = 0;
gui_list->selected_item = 0; gui_list->selected_item = 0;
#ifdef HAVE_TOUCHSCREEN
gui_list->y_pos = 0;
#endif
FOR_NB_SCREENS(i) FOR_NB_SCREENS(i)
{ {
gui_list->start_item[i] = 0; gui_list->start_item[i] = 0;
@ -282,6 +285,9 @@ static void gui_list_put_selection_on_screen(struct gui_synclist * gui_list,
gui_list->start_item[screen] = bottom; gui_list->start_item[screen] = bottom;
else else
gui_list->start_item[screen] = new_start_item; gui_list->start_item[screen] = new_start_item;
#ifdef HAVE_TOUCHSCREEN
gui_list->y_pos = gui_list->start_item[SCREEN_MAIN] * gui_list->line_height[SCREEN_MAIN];
#endif
} }
static void edge_beep(struct gui_synclist * gui_list, bool wrap) static void edge_beep(struct gui_synclist * gui_list, bool wrap)
@ -417,6 +423,10 @@ static void gui_list_select_at_offset(struct gui_synclist * gui_list,
gui_list->selected_size); gui_list->selected_size);
gui_list->selected_item = gui_list->start_item[i] + nb_lines; gui_list->selected_item = gui_list->start_item[i] + nb_lines;
} }
#ifdef HAVE_TOUCHSCREEN
gui_list->y_pos = gui_list->start_item[SCREEN_MAIN] * gui_list->line_height[SCREEN_MAIN];
#endif
} }
return; return;
} }
@ -667,7 +677,7 @@ bool gui_synclist_do_button(struct gui_synclist * lists,
action = *actionptr = gui_synclist_do_touchscreen(lists); action = *actionptr = gui_synclist_do_touchscreen(lists);
else if (action > ACTION_TOUCHSCREEN_MODE) else if (action > ACTION_TOUCHSCREEN_MODE)
/* cancel kinetic if we got a normal button event */ /* cancel kinetic if we got a normal button event */
_gui_synclist_stop_kinetic_scrolling(); _gui_synclist_stop_kinetic_scrolling(lists);
#endif #endif
/* Disable the skin redraw callback */ /* Disable the skin redraw callback */

View file

@ -118,6 +118,10 @@ struct gui_synclist
bool scroll_all; bool scroll_all;
int nb_items; int nb_items;
int selected_item; int selected_item;
#ifdef HAVE_TOUCHSCREEN
/* absolute Y coordinate, used for smooth scrolling */
int y_pos;
#endif
int start_item[NB_SCREENS]; /* the item that is displayed at the top of the screen */ int start_item[NB_SCREENS]; /* the item that is displayed at the top of the screen */
/* the number of lines that are selected at the same time */ /* the number of lines that are selected at the same time */
int selected_size; int selected_size;
@ -229,7 +233,7 @@ int skinlist_get_line_count(enum screen_type screen, struct gui_synclist *list);
/* this needs to be fixed if we ever get more than 1 touchscreen on a target */ /* this needs to be fixed if we ever get more than 1 touchscreen on a target */
extern unsigned gui_synclist_do_touchscreen(struct gui_synclist * gui_list); extern unsigned gui_synclist_do_touchscreen(struct gui_synclist * gui_list);
/* only for private use in gui/list.c */ /* only for private use in gui/list.c */
extern void _gui_synclist_stop_kinetic_scrolling(void); extern void _gui_synclist_stop_kinetic_scrolling(struct gui_synclist * gui_list);
#endif #endif
/* If the list has a pending postponed scheduled announcement, that /* If the list has a pending postponed scheduled announcement, that