summaryrefslogtreecommitdiff
path: root/apps/gui/bitmap
diff options
context:
space:
mode:
authorThomas Martitz <kugel@rockbox.org>2012-04-08 13:15:39 +0200
committerThomas Martitz <kugel@rockbox.org>2012-04-08 13:16:54 +0200
commit688302a3b3cee640c268ba767b469fec5bfa2ca8 (patch)
tree33f8aecb42dc8e8adfa015f0e272faba344f2f96 /apps/gui/bitmap
parenta12b2a5df96233465a2811f9b056e15d7ff30ee2 (diff)
touchscreen: Rewrite kinetic scrolling using a state machine.
The old code was very confusing. The scrolling modes need to be handled differently, thus a state machine makes sense. Should fix numerious glitches and be easier to maintain. NOTE: Behavior is still a bit glitchy with an SBS in use, because the skin engine sees the touch events earlier than the list code. Change-Id: I4ccead359c81de0d0fc3dea636fe2cb3a28d1bc6
Diffstat (limited to 'apps/gui/bitmap')
-rw-r--r--apps/gui/bitmap/list.c416
1 files changed, 213 insertions, 203 deletions
diff --git a/apps/gui/bitmap/list.c b/apps/gui/bitmap/list.c
index 2f13d8ba53..05a7f066af 100644
--- a/apps/gui/bitmap/list.c
+++ b/apps/gui/bitmap/list.c
@@ -48,17 +48,8 @@
static struct viewport list_text[NB_SCREENS], title_text[NB_SCREENS];
#ifdef HAVE_TOUCHSCREEN
-/* difference in pixels between draws, above it means enough to start scrolling */
-#define SCROLL_BEGIN_THRESHOLD 3
-
-static enum {
- SCROLL_NONE, /* no scrolling */
- SCROLL_BAR, /* scroll by using the scrollbar */
- SCROLL_SWIPE, /* scroll by wiping over the screen */
- SCROLL_KINETIC, /* state after releasing swipe */
-} scroll_mode;
-
static int y_offset;
+static bool hide_selection = true;
#endif
int gui_list_get_item_offset(struct gui_synclist * gui_list, int item_width,
@@ -90,6 +81,17 @@ void gui_synclist_scroll_stop(struct gui_synclist *lists)
Note: This image is flipped horizontally when the language is a
right-to-left one (Hebrew, Arabic)
*/
+
+static int list_icon_width(enum screen_type screen)
+{
+ return get_icon_width(screen) + ICON_PADDING * 2;
+}
+
+static int list_icon_height(enum screen_type screen)
+{
+ return get_icon_height(screen);
+}
+
static bool draw_title(struct screen *display, struct gui_synclist *list)
{
const int screen = display->screen_type;
@@ -108,9 +110,9 @@ static bool draw_title(struct screen *display, struct gui_synclist *list)
{
struct viewport title_icon = *title_text_vp;
- title_icon.width = get_icon_width(screen) + ICON_PADDING * 2;
- title_icon.y += (title_icon.height - get_icon_height(screen)) / 2;
- title_icon.height = get_icon_height(screen);
+ title_icon.width = list_icon_width(screen);
+ title_icon.y += (title_icon.height - list_icon_height(screen)) / 2;
+ title_icon.height = list_icon_height(screen);
if (VP_IS_RTL(&title_icon))
{
title_icon.x += title_text_vp->width - title_icon.width;
@@ -141,7 +143,7 @@ void list_draw(struct screen *display, struct gui_synclist *list)
int start, end, line_height, style, item_offset, i;
const int screen = display->screen_type;
const int list_start_item = list->start_item[screen];
- const int icon_width = get_icon_width(screen) + ICON_PADDING;
+ const int icon_width = list_icon_width(screen);
const bool scrollbar_in_left = (global_settings.scrollbar == SCROLLBAR_LEFT);
const bool show_cursor = !global_settings.cursor_style &&
list->show_selection_marker;
@@ -236,7 +238,7 @@ void list_draw(struct screen *display, struct gui_synclist *list)
list_icons.x += list_text_vp->width + ICON_PADDING;
else
list_text_vp->x += list_icons.width + ICON_PADDING;
- icon_yoffset = (line_height - get_icon_height(screen)) / 2;
+ icon_yoffset = (line_height - list_icon_height(screen)) / 2;
}
for (i=start; i<end && i<list->nb_items; i++)
@@ -274,7 +276,7 @@ void list_draw(struct screen *display, struct gui_synclist *list)
if(
#ifdef HAVE_TOUCHSCREEN
/* don't draw it during scrolling */
- scroll_mode == SCROLL_NONE &&
+ !hide_selection &&
#endif
i >= list->selected_item
&& i < list->selected_item + list->selected_size
@@ -338,7 +340,7 @@ void list_draw(struct screen *display, struct gui_synclist *list)
display->set_viewport(&list_icons);
if (list->callback_get_item_icon != NULL)
{
- int xoff = show_cursor ? get_icon_width(screen) + ICON_PADDING : 0;
+ int xoff = show_cursor ? list_icon_width(screen) : 0;
screen_put_iconxy(display, xoff,
line*line_height + draw_offset + icon_yoffset,
list->callback_get_item_icon(i, list->data));
@@ -360,13 +362,15 @@ void list_draw(struct screen *display, struct gui_synclist *list)
#if defined(HAVE_TOUCHSCREEN)
/* This needs to be fixed if we ever get more than 1 touchscreen on a target. */
-static bool released = false;
+/* difference in pixels between draws, above it means enough to start scrolling */
+#define SCROLL_BEGIN_THRESHOLD 3
-/* Used for kinetic scrolling as we need to know the last position to
- * recognize the scroll direction.
- * This gets reset to 0 at the end of scrolling
- */
-static int last_position=0;
+static enum {
+ SCROLL_NONE, /* no scrolling */
+ SCROLL_BAR, /* scroll by using the scrollbar */
+ SCROLL_SWIPE, /* scroll by wiping over the screen */
+ SCROLL_KINETIC, /* state after releasing swipe */
+} scroll_mode;
static int scrollbar_scroll(struct gui_synclist * gui_list,
int y)
@@ -486,6 +490,7 @@ void _gui_synclist_stop_kinetic_scrolling(void)
if (scroll_mode == SCROLL_KINETIC)
kinetic_force_stop();
scroll_mode = SCROLL_NONE;
+ hide_selection = false;
}
/*
* returns false if scrolling should be stopped entirely
@@ -497,11 +502,12 @@ void _gui_synclist_stop_kinetic_scrolling(void)
static int scroll_begin_threshold;
static int threshold_accumulation;
-static bool swipe_scroll(struct gui_synclist * gui_list, int line_height, int difference)
+static bool swipe_scroll(struct gui_synclist * gui_list, int difference)
{
/* fixme */
const enum screen_type screen = screens[SCREEN_MAIN].screen_type;
const int nb_lines = viewport_get_nb_lines(&list_text[screen]);
+ const int line_height = gui_list->parent[0]->line_height;
if (UNLIKELY(scroll_begin_threshold == 0))
scroll_begin_threshold = touchscreen_get_scroll_threshold();
@@ -566,13 +572,12 @@ static int kinetic_callback(struct timeout *tmo)
return 0;
struct cb_data *data = (struct cb_data*)tmo->data;
- int line_height = data->list->parent[0]->line_height;
/* ds = v*dt */
int pixel_diff = data->velocity * RELOAD_INTERVAL / HZ;
/* remember signedness to detect stopping */
int old_sign = SIGN(data->velocity);
/* advance the list */
- if (!swipe_scroll(data->list, line_height, pixel_diff))
+ if (!swipe_scroll(data->list, pixel_diff))
{
/* nothing to scroll? */
data->velocity = 0;
@@ -587,6 +592,8 @@ static int kinetic_callback(struct timeout *tmo)
data->velocity = 0;
}
+ /* let get_action() timeout, which loads to a
+ * gui_synclist_draw() call from the main thread */
queue_post(&button_queue, BUTTON_REDRAW, 0);
/* stop if the velocity hit or crossed zero */
if (!data->velocity)
@@ -594,8 +601,6 @@ static int kinetic_callback(struct timeout *tmo)
kinetic_stats_reset();
return 0;
}
- /* let get_action() timeout, which loads to a
- * gui_synclist_draw() call from the main thread */
return RELOAD_INTERVAL; /* cancel or reload */
}
@@ -628,208 +633,213 @@ static bool kinetic_setup_scroll(struct gui_synclist *list)
return false;
}
-unsigned gui_synclist_do_touchscreen(struct gui_synclist * gui_list)
-{
- short x, y;
- const enum screen_type screen = SCREEN_MAIN;
- struct viewport *parent = gui_list->parent[screen];
- const int button = action_get_touchscreen_press_in_vp(&x, &y, parent);
- const int list_start_item = gui_list->start_item[screen];
- const int line_height = gui_list->parent[screen]->line_height;
- const struct viewport *list_text_vp = &list_text[screen];
- const bool old_released = released;
- const bool show_title = list_display_title(gui_list, screen);
- const bool show_cursor = !global_settings.cursor_style &&
- gui_list->show_selection_marker;
- const bool on_title_clicked = show_title && y < line_height && (button&BUTTON_REL);
- const bool cancelled_kinetic = (scroll_mode == SCROLL_KINETIC
- && button != ACTION_NONE && button != ACTION_UNKNOWN
- && !is_kinetic_over());
- int icon_width = 0;
- int line, list_width = list_text_vp->width;
+#define OUTSIDE 0
+#define TITLE_TEXT (1<<0)
+#define TITLE_ICON (1<<1)
+#define SCROLLBAR (1<<2)
+#define LIST_TEXT (1<<3)
+#define LIST_ICON (1<<4)
- released = (button&BUTTON_REL) != 0;
-
- if (button == ACTION_NONE || button == ACTION_UNKNOWN)
- {
- /* this happens when we hit edges of the list while kinetic scrolling,
- * but not when manually cancelling */
- if (scroll_mode == SCROLL_KINETIC)
- return ACTION_REDRAW;
- return ACTION_NONE;
- }
+#define TITLE (TITLE_TEXT|TITLE_ICON)
+#define LIST (LIST_TEXT|LIST_ICON)
- /* x and y are relative to parent */
- if (gui_list->callback_get_item_icon != NULL)
- icon_width += get_icon_width(screen);
- if (show_cursor)
- icon_width += get_icon_width(screen);
+static int get_click_location(struct gui_synclist *list, int x, int y)
+{
+ int screen = SCREEN_MAIN;
+ struct viewport *parent, *title, *text;
+ int retval = OUTSIDE;
- if (on_title_clicked)
+ parent = list->parent[screen];
+ if (viewport_point_within_vp(parent, x, y))
{
- if (scroll_mode == SCROLL_NONE || is_kinetic_over())
+ /* see if the title was clicked */
+ title = &title_text[screen];
+ if (viewport_point_within_vp(title, x, y))
+ retval = TITLE_TEXT;
+ /* check the icon too */
+ if (list->title_icon != Icon_NOICON && global_settings.show_icons)
+ {
+ int width = list_icon_width(screen);
+ struct viewport vp = *title;
+ if (VP_IS_RTL(&vp))
+ vp.x += vp.width;
+ else
+ vp.x -= width;
+ vp.width = width;
+ if (viewport_point_within_vp(&vp, x, y))
+ retval = TITLE_ICON;
+ }
+ /* check scrollbar. assume it's shown, if it isn't it will be handled
+ * later */
+ if (retval == OUTSIDE)
{
- if (x < icon_width)
+ bool on_scrollbar_clicked;
+ int adj_x = x - parent->x;
+ switch (global_settings.scrollbar)
{
- /* Top left corner is GO_TO_ROOT */
- if (button == BUTTON_REL)
- return ACTION_STD_MENU;
- else if (button == (BUTTON_REPEAT|BUTTON_REL))
- return ACTION_STD_CONTEXT;
- return ACTION_NONE;
+ case SCROLLBAR_LEFT:
+ on_scrollbar_clicked = adj_x <= SCROLLBAR_WIDTH; break;
+ case SCROLLBAR_RIGHT:
+ on_scrollbar_clicked = adj_x > (title->x + title->width - SCROLLBAR_WIDTH); break;
+ default:
+ on_scrollbar_clicked = false; break;
}
- else /* click on title text is cancel */
- if (button == BUTTON_REL)
- return ACTION_STD_CANCEL;
+ if (on_scrollbar_clicked)
+ retval = SCROLLBAR;
}
- /* do this after the above so the scrolling stops without
- * going back in the list with the same touch */
- if (scroll_mode == SCROLL_KINETIC)
+ if (retval == OUTSIDE)
{
- kinetic_force_stop();
- scroll_mode = SCROLL_NONE;
+ text = &list_text[screen];
+ if (viewport_point_within_vp(text, x, y))
+ retval = LIST_TEXT;
+ else /* if all fails, it must be on the list icons */
+ retval = LIST_ICON;
}
}
- else /* list area clicked (or not released) */
- {
- const int actual_y = y - (show_title ? line_height : 0);
- bool on_scrollbar_clicked;
- switch (global_settings.scrollbar)
- {
- case SCROLLBAR_LEFT:
- on_scrollbar_clicked = x <= SCROLLBAR_WIDTH; break;
- case SCROLLBAR_RIGHT:
- on_scrollbar_clicked = x > (icon_width + list_width); break;
- default:
- on_scrollbar_clicked = false; break;
- }
- /* conditions for scrollbar scrolling:
- * * pen is on the scrollbar
- * AND scrollbar is on the right (left case is handled above)
- * OR * pen is in the somewhere else but we did scrollbar scrolling before
- *
- * scrollbar scrolling must end if the pen is released
- * scrollbar scrolling must not happen if we're currently scrolling
- * via swiping the screen
- **/
-
- if (!released && scroll_mode != SCROLL_SWIPE &&
- (on_scrollbar_clicked || scroll_mode == SCROLL_BAR))
- {
- if (scroll_mode == SCROLL_KINETIC)
- kinetic_force_stop();
- scroll_mode = SCROLL_BAR;
- return scrollbar_scroll(gui_list, y);
- }
+ return retval;
+}
- /* |--------------------------------------------------------|
- * | Description of the touchscreen list interface: |
- * |--------------------------------------------------------|
- * | Pressing an item will select it and "enter" it. |
- * | |
- * | Pressing and holding your pen down will scroll through |
- * | the list of items. |
- * | |
- * | Pressing and holding your pen down on a single item |
- * | will bring up the context menu of it. |
- * |--------------------------------------------------------|
- */
- if (actual_y > 0 || button & BUTTON_REPEAT)
+unsigned gui_synclist_do_touchscreen(struct gui_synclist * list)
+{
+ enum screen_type screen;
+ struct viewport *parent;
+ short x, y;
+ int action, adj_y, line, line_height, list_start_item;
+ bool recurse;
+ static int last_y;
+
+ screen = SCREEN_MAIN;
+ parent = list->parent[screen];
+ line_height = list->parent[screen]->line_height;
+ list_start_item = list->start_item[screen];
+ /* start with getting the action code and finding the click location */
+ action = action_get_touchscreen_press(&x, &y);
+ adj_y = y - parent->y;
+
+ /* selection needs to be corrected if items are only partially visible */
+ line = (adj_y - y_offset) / line_height;
+ if (list_display_title(list, screen))
+ line -= 1; /* adjust for the list title */
+
+ /* some defaults before running the state machine */
+ recurse = false;
+ hide_selection = true;
+
+ switch (scroll_mode)
+ {
+ case SCROLL_NONE:
{
- /* selection needs to be corrected if an items are only
- * partially visible */
- line = (actual_y - y_offset) / line_height;
-
- if (cancelled_kinetic)
- {
- kinetic_force_stop();
- scroll_mode = SCROLL_SWIPE;
- }
-
- /* Pressed below the list*/
- if (list_start_item + line >= gui_list->nb_items)
- {
- /* don't collect last_position outside of the list area
- * it'd break selecting after such a situation */
- last_position = 0;
- return ACTION_NONE;
+ list->selected_item = list_start_item+line;
+ gui_synclist_speak_item(list);
+ if (!last_y)
+ { /* first run. register adj_y and re-run (will then take the else case) */
+ last_y = adj_y;
+ recurse = true;
}
-
- if (button & BUTTON_REPEAT && scroll_mode == SCROLL_NONE)
- {
- /* held a single line for a while, bring up the context menu */
- gui_synclist_select_item(gui_list, list_start_item + line);
- /* don't sent context repeatedly */
- action_wait_for_release();
- last_position = 0;
- return ACTION_STD_CONTEXT;
- }
- if (released && !cancelled_kinetic)
+ else
{
- /* Pen was released anywhere on the screen */
- last_position = 0;
- if (scroll_mode == SCROLL_NONE)
+ int click_loc = get_click_location(list, x, y);
+ if (action == BUTTON_TOUCHSCREEN)
{
- /* select current line */
- gui_synclist_select_item(gui_list, list_start_item + line);
- return ACTION_STD_OK;
+ /* if not scrolling, the user is trying to select */
+ int diff = adj_y - last_y;
+ if ((click_loc & LIST) && swipe_scroll(list, diff))
+ scroll_mode = SCROLL_SWIPE;
+ else if (click_loc & SCROLLBAR)
+ scroll_mode = SCROLL_BAR;
+
+ hide_selection = click_loc & SCROLLBAR;
}
- else
+ else if (action == BUTTON_REPEAT)
{
- /* we were scrolling
- * -> reset scrolling but do nothing else */
- if (scroll_mode == SCROLL_SWIPE)
+ if (click_loc & LIST)
{
- if (kinetic_setup_scroll(gui_list))
- scroll_mode = SCROLL_KINETIC;
+ /* held a single line for a while, bring up the context menu */
+ gui_synclist_select_item(list, list_start_item + line);
+ /* don't sent context repeatedly */
+ action_wait_for_release();
+ last_y = 0;
+ return ACTION_STD_CONTEXT;
}
- if (scroll_mode != SCROLL_KINETIC)
- scroll_mode = SCROLL_NONE;
- return ACTION_NONE;
}
- }
- else
- { /* pen is on the screen */
- bool redraw = false, result = false;
- /* beginning of list interaction denoted by release in
- * the previous call */
- if (old_released || is_kinetic_over())
+ else if (action & BUTTON_REL)
{
- scroll_mode = SCROLL_NONE;
- redraw = true;
- }
-
- /* select current item; gui_synclist_select_item()
- * is not called because it has side effects that
- * disturb kinetic scrolling */
- gui_list->selected_item = list_start_item+line;
- gui_synclist_speak_item(gui_list);
- if (last_position == 0)
- {
- redraw = true;
- last_position = actual_y;
- }
- else
- {
- /* record speed data in case we do kinetic scrolling */
- int diff = actual_y - last_position;
- kinetic_stats_collect(diff);
- result = swipe_scroll(gui_list, line_height, diff);
+ last_y = 0;
+ if (click_loc & LIST)
+ { /* release on list item enters it */
+ gui_synclist_select_item(list, list_start_item + line);
+ return ACTION_STD_OK;
+ }
+ else if (click_loc & TITLE_TEXT)
+ { /* clicking the title goes one level up (cancel) */
+ return ACTION_STD_CANCEL;
+ }
+ else if (click_loc & TITLE_ICON)
+ { /* clicking the title icon goes back to the root */
+ return ACTION_STD_MENU;
+ }
}
-
- /* Start scrolling once the pen is moved without
- * releasing it inbetween */
- if (result)
+ }
+ break;
+ }
+ case SCROLL_SWIPE:
+ {
+ /* when swipe scrolling, we accept outside presses as well and
+ * grab the entire screen (i.e. click_loc does not matter) */
+ int diff = adj_y - last_y;
+ kinetic_stats_collect(diff);
+ if (swipe_scroll(list, diff))
+ {
+ /* letting the pen go enters kinetic scrolling */
+ if ((action & BUTTON_REL))
{
- redraw = true;
- scroll_mode = SCROLL_SWIPE;
+ if (kinetic_setup_scroll(list))
+ scroll_mode = SCROLL_KINETIC;
+ else
+ scroll_mode = SCROLL_NONE;
}
- last_position = actual_y;
-
- return redraw ? ACTION_REDRAW:ACTION_NONE;
}
+ else if (action & BUTTON_REL)
+ {
+ scroll_mode = SCROLL_NONE;
+ }
+ break;
+ }
+ case SCROLL_KINETIC:
+ {
+ /* during kinetic scrolling we need to handle cancellation.
+ * This state is actually only entered upon end of it as this
+ * function is not called during the animation. */
+ if (!is_kinetic_over())
+ { /* a) the user touched the screen (manual cancellation) */
+ kinetic_force_stop();
+ scroll_mode = SCROLL_SWIPE;
+ }
+ else
+ { /* b) kinetic scrolling stopped on its own */
+ /* need to re-run this with SCROLL_NONE since otherwise
+ * the next touch is not detected correctly */
+ scroll_mode = SCROLL_NONE;
+ recurse = true;
+ }
+ break;
+ }
+ case SCROLL_BAR:
+ {
+ /* similarly to swipe scroll, using the scrollbar grabs
+ * focus so the click location is irrelevant */
+ scrollbar_scroll(list, adj_y);
+ if (action & BUTTON_REL)
+ scroll_mode = SCROLL_NONE;
+ break;
}
}
- return ACTION_REDRAW;
+
+ /* register y position unless forcefully reset to 0 */
+ if (last_y)
+ last_y = adj_y;
+
+ return recurse ? gui_synclist_do_touchscreen(list) : ACTION_REDRAW;
}
+
#endif