touchscreen: Kinetic scrolling v2
rockbox-gerrit-noreply--- via rockbox-cvs <[email protected]>
| Newsgroups | gmane.comp.systems.archos.rockbox.cvs |
|---|---|
| Message-ID | <[email protected]> |
commit c93c7bfdcb319025d0f19034c4e37b74997888d1 Author: Aidan MacDonald <[email protected]> Date: Sat Nov 26 21:43:17 2022 +0000 touchscreen: Kinetic scrolling v2 Add configurable acceleration settings. There are 3 settings which are applied at different times during scrolling. "accel" is used to calculate the acceleration applied to the list based on the swipe speed. "decel" determines the deceleration force applied based on the list scroll speed, and "press" is an additional deceleration force applied if you are pressing on the screen. All settings have the general form: kinetic scroll accel: <a0> <a1> <delay> kinetic scroll decel: <a0> <a1> <delay> kinetic scroll press: <a0> <a1> <delay> where the formula for the acceleration is "accel = a0 + a1*vel" and "vel" is the input velocity (swipe speed for accel, list scroll speed for decel/press). The delay parameter is a value in milliseconds which acts as a grace period where the acceleration will *not* be applied. For example, if you specify 250ms for decel, then deceleration will not kick in until 250ms after scrolling starts. For press, the delay is based on the duration of the touchscreen press. All settings have sane default values and don't need to be configured to enable kinetic scrolling; it is always enabled. But the settings are there for those who want to tweak the behavior. Change-Id: I9723b496b2b5d3fb3643af60b169754fdba497f0 diff --git a/apps/gui/bitmap/list.c b/apps/gui/bitmap/list.c index be0267fcc5..3d77afc929 100644 --- a/apps/gui/bitmap/list.c +++ b/apps/gui/bitmap/list.c @@ -41,6 +41,7 @@ #include "statusbar-skinned.h" #include "debug.h" #include "line.h" +#include "fixedpoint.h" #define ICON_PADDING 1 #define ICON_PADDING_S "1" @@ -540,25 +541,15 @@ static int swipe_scroll(struct gui_synclist *gui_list, int delta) */ #define SIGN(a) ((a) < 0 ? -1 : 1) -/* these could possibly be configurable */ -/* the lower the smoother */ #define RELOAD_INTERVAL (HZ/25) -/* deceleration factors: new velocity = P * old velocity * - K */ -#define DECEL_P (RELOAD_INTERVAL * 1) / (2 * HZ) -#define DECEL_K (RELOAD_INTERVAL * 2 * LCD_HEIGHT / HZ) -/* fast deceleration when the screen is pressed */ -#define FINGER_DECEL_P (RELOAD_INTERVAL * 2) / (1 * HZ) -#define FINGER_DECEL_K (RELOAD_INTERVAL * (LCD_HEIGHT * 4) / HZ) -/* finger deceleration does not kick in until the touch duration exceeds this */ -#define FINGER_DECEL_GRACE (HZ/4) -/* fraction used to multiply gesture velocity before accumulating it */ -#define VELOCITY_RESIST 3 / 4 +#define RELOAD_INTERVAL_FP ((RELOAD_INTERVAL << LIST_KINETIC_FRACBITS) / HZ) struct kinetic_cb_data { struct gui_synclist *list; - int velocity; - int subpixel_accum; - long finger_tick; + long velocity; + long distance; + long scroll_duration; + long press_duration; }; struct kinetic { @@ -570,12 +561,31 @@ struct kinetic { static struct kinetic kinetic; static struct gesture_vel list_gvel; +const struct list_kinetic_scroll_settings list_kinetic_scroll_accel_default = { + .a0 = 1000 << LIST_KINETIC_FRACBITS, + .a1 = 1 << (LIST_KINETIC_FRACBITS - 1), + /* delay is ignored for this one */ +}; + +const struct list_kinetic_scroll_settings list_kinetic_scroll_decel_default = { + .a0 = 3000 << LIST_KINETIC_FRACBITS, + .a1 = 1 << (LIST_KINETIC_FRACBITS - 1), + .delay = 125 * HZ / 1000, +}; + +const struct list_kinetic_scroll_settings list_kinetic_scroll_press_default = { + .a0 = 35000 << LIST_KINETIC_FRACBITS, + .a1 = 4 << LIST_KINETIC_FRACBITS, + .delay = 250 * HZ / 1000, +}; + static void kinetic_stop_scrolling(struct kinetic *k, struct gui_synclist *list) { if (k->cb_data.list == list) { - k->cb_data.subpixel_accum = 0; + k->cb_data.list = NULL; k->cb_data.velocity = 0; + k->cb_data.distance = 0; timeout_cancel(&k->tmo); } } @@ -590,22 +600,39 @@ void _gui_synclist_stop_kinetic_scrolling(struct gui_synclist *list) } } +static long kinetic_calc_accel(long input, long duration, + const struct list_kinetic_scroll_settings *param) +{ + if (duration < param->delay) + return 0; + + long x1 = input < 0 ? -input : input; + long r = fp_mul(param->a1, x1, LIST_KINETIC_FRACBITS) + + param->a0 + (1 << (LIST_KINETIC_FRACBITS - 1)); + + return SIGN(input) * r; +} + static int kinetic_callback(struct timeout *tmo) { struct kinetic_cb_data *data = (struct kinetic_cb_data*)tmo->data; struct gui_synclist *list = data->list; + int pixel_diff, action; /* deal with cancellation */ - if (list->scroll_mode != SCROLL_KINETIC) + if (!list || list->scroll_mode != SCROLL_KINETIC) return 0; - /* ds = v*dt */ - data->subpixel_accum += 100 * data->velocity * RELOAD_INTERVAL / HZ; + long abs_vel = data->velocity < 0 ? -data->velocity : data->velocity; + long vel_sgn = SIGN(data->velocity); + + data->distance += fp_mul(abs_vel, RELOAD_INTERVAL_FP, LIST_KINETIC_FRACBITS); + data->scroll_duration += RELOAD_INTERVAL; - int pixel_diff = data->subpixel_accum / 100; - data->subpixel_accum %= 100; + pixel_diff = data->distance >> LIST_KINETIC_FRACBITS; + data->distance -= pixel_diff << LIST_KINETIC_FRACBITS; - int action = swipe_scroll(list, pixel_diff); + action = swipe_scroll(list, pixel_diff * vel_sgn); if (action == ACTION_REDRAW) { /* force the list to redraw */ @@ -613,29 +640,23 @@ static int kinetic_callback(struct timeout *tmo) } /* calculate and apply deceleration */ - int sign = SIGN(data->velocity); - int absvel = abs(data->velocity); - int decel; - - if (data->finger_tick != 0 && - !TIME_BEFORE(current_tick, data->finger_tick + FINGER_DECEL_GRACE)) - { - decel = data->velocity * FINGER_DECEL_P + sign * FINGER_DECEL_K; - } - else - { - decel = data->velocity * DECEL_P + sign * DECEL_K; - } + long abs_decel = 0; + abs_decel += kinetic_calc_accel(abs_vel, data->scroll_duration, + &global_settings.kinetic_scroll_decel); + abs_decel += kinetic_calc_accel(abs_vel, data->press_duration, + &global_settings.kinetic_scroll_press); + abs_decel = fp_mul(abs_decel, RELOAD_INTERVAL_FP, LIST_KINETIC_FRACBITS); /* - * Ensure velocity is smoothly reduced to zero to avoid jerkiness. + * Ensure velocity is smoothly reduced to zero to avoid jerky + * scrolling near the end of scrolling. */ - if (absvel < 3) + if (abs_vel < (3 << LIST_KINETIC_FRACBITS)) data->velocity = 0; - else if (absvel < sign*decel) - data->velocity = data->velocity / 3; + else if (abs_vel < abs_decel) + data->velocity /= 3; else - data->velocity -= decel; + data->velocity -= SIGN(data->velocity) * abs_decel; /* stop scrolling if we didn't move, it means we hit the end */ if (list->y_pos == list->scroll_base_y && pixel_diff != 0) @@ -661,8 +682,27 @@ static bool kinetic_start_scrolling(struct kinetic *k, struct gui_synclist *list if (yvel == 0) return false; + long yvel_fp = yvel << LIST_KINETIC_FRACBITS; + if (list->scroll_mode == SCROLL_KINETIC) + { + long accel = kinetic_calc_accel(k->cb_data.velocity, LONG_MAX, + &global_settings.kinetic_scroll_accel); + accel = fp_mul(accel, RELOAD_INTERVAL_FP, LIST_KINETIC_FRACBITS); + + /* the acceleration is in the direction of the swipe */ + if (SIGN(accel) != SIGN(yvel_fp)) + accel = -accel; + + yvel_fp += accel; + } + k->cb_data.list = list; - k->cb_data.velocity += yvel * VELOCITY_RESIST; + k->cb_data.velocity += yvel_fp; + if (list->scroll_mode != SCROLL_KINETIC) + { + k->cb_data.distance = 0; + k->cb_data.scroll_duration = 0; + } list->scroll_mode = SCROLL_KINETIC; list->scroll_base_y = list->y_pos; @@ -766,9 +806,9 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist *list) int click_loc; if (action_gesture_is_pressed()) - kinetic.cb_data.finger_tick = gevent.start_tick; + kinetic.cb_data.press_duration = gevent.last_tick - gevent.start_tick; else - kinetic.cb_data.finger_tick = 0; + kinetic.cb_data.press_duration = -1; switch (gevent.id) { diff --git a/apps/gui/list.h b/apps/gui/list.h index ad31180c4f..534558c0bc 100644 --- a/apps/gui/list.h +++ b/apps/gui/list.h @@ -189,6 +189,36 @@ struct gui_synclist #endif }; +#ifdef HAVE_TOUCHSCREEN +#define LIST_KINETIC_FRACBITS 8 + +struct list_kinetic_scroll_settings +{ + /* Coefficients for a polynomial a1*x + a0, in fixed point. + * x is the kinetic scroll speed in pixels/sec (always positive) */ + long a0; + long a1; + + /* Delay (in ticks) during which the acceleration is not applied. */ + long delay; +}; + +/* Acceleration applied to the scroll velocity when a swipe occurs during + * kinetic scrolling. This is needed to counteract the deceleration term + * for high scrolling speeds, since the user can only input a low constant + * acceleration from swiping alone. Delay is ignored. */ +extern const struct list_kinetic_scroll_settings list_kinetic_scroll_accel_default; + +/* Deceleration applied to the scroll velocity during kinetic scrolling. + * The delay occurs right at the beginning of scrolling, eg. a delay of + * 1 second means deceleration starts 1 second after scrolling. */ +extern const struct list_kinetic_scroll_settings list_kinetic_scroll_decel_default; + +/* Deceleration applied during kinetic scrolling when the screen is + * pressed for longer than the delay interval (motion is not required). + * This is additive with list_kinetic_scroll_decel. */ +extern const struct list_kinetic_scroll_settings list_kinetic_scroll_press_default; +#endif extern void list_init(void); diff --git a/apps/settings.h b/apps/settings.h index 0fc07a2413..35af24c97d 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -31,6 +31,7 @@ #include "button.h" #include "audio.h" #include "dsp_proc_settings.h" +#include "gui/list.h" struct opt_items { unsigned const char* string; @@ -799,6 +800,9 @@ struct user_settings #ifdef HAVE_TOUCHSCREEN int touch_mode; struct touchscreen_parameter ts_calibration_data; + struct list_kinetic_scroll_settings kinetic_scroll_accel; + struct list_kinetic_scroll_settings kinetic_scroll_decel; + struct list_kinetic_scroll_settings kinetic_scroll_press; #endif #ifdef HAVE_PITCHCONTROL diff --git a/apps/settings_list.c b/apps/settings_list.c index 6821671d05..958e61f07f 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -42,6 +42,7 @@ #include "open_plugin.h" #include "misc.h" #include "playback.h" +#include "fixedpoint.h" #ifdef HAVE_REMOTE_LCD #include "lcd-remote.h" #endif @@ -950,6 +951,77 @@ static void tsc_set_default(void* setting, void* defaultval) { memcpy(setting, defaultval, sizeof(struct touchscreen_parameter)); } + +static void list_kinetic_load_from_cfg(void *setting, char *value) +{ + struct list_kinetic_scroll_settings *param = setting; + long vals[4] = { 0, 0, 0 }; + int count = 0; + + while (*value && count < 3) + { + while (isspace (*value)) + value++; + + int num = atoi(value); + while (!isspace(*value)) + value++; + + if (count == 2) + /* delay in milliseconds */ + vals[count] = HZ * num / 1000; + else + { + /* polynomial coefficients in 1/100th scale */ + int sign = 1; + if (num < 0) + { + num = -num; + sign = -1; + } + + int frac = num % 100; + num /= 100; + + num <<= LIST_KINETIC_FRACBITS; + frac <<= LIST_KINETIC_FRACBITS; + vals[count] = num + fp_div(frac, 100, LIST_KINETIC_FRACBITS); + vals[count] *= sign; + } + + count++; + } + + param->a1 = vals[0]; + param->a0 = vals[1]; + param->delay = vals[2]; +} + +static char *list_kinetic_write_to_cfg(void *setting, char *buf, int buf_len) +{ + struct list_kinetic_scroll_settings *param = setting; + long vals[2] = { param->a1, param->a0 }; + + for (int i = 0; i < 2; ++i) + { + long num = vals[i] >> LIST_KINETIC_FRACBITS; + long frac = vals[i] - (num << LIST_KINETIC_FRACBITS); + vals[i] = (num * 100) + (frac * 100) / (1 << LIST_KINETIC_FRACBITS); + } + + snprintf(buf, buf_len, "%ld %ld %ld", + vals[0], vals[1], 1000 * param->delay / HZ); + return buf; +} + +static bool list_kinetic_is_default(void *setting, void *defaultval) +{ + return memcmp(setting, defaultval, sizeof(struct list_kinetic_scroll_settings)) != 0; +} +static void list_kinetic_set_default(void *setting, void *defaultval) +{ + memcpy(setting, defaultval, sizeof(struct list_kinetic_scroll_settings)); +} #endif static void start_in_callback(int var) @@ -2353,6 +2425,18 @@ const struct settings_list settings[] = { &default_calibration_parameters, "touchscreen calibration", tsc_load_from_cfg, tsc_write_to_cfg, tsc_is_changed, tsc_set_default), + CUSTOM_SETTING(0, kinetic_scroll_accel, -1, + &list_kinetic_scroll_accel_default, "kinetic scroll accel", + list_kinetic_load_from_cfg, list_kinetic_write_to_cfg, + list_kinetic_is_default, list_kinetic_set_default), + CUSTOM_SETTING(0, kinetic_scroll_decel, -1, + &list_kinetic_scroll_decel_default, "kinetic scroll decel", + list_kinetic_load_from_cfg, list_kinetic_write_to_cfg, + list_kinetic_is_default, list_kinetic_set_default), + CUSTOM_SETTING(0, kinetic_scroll_press, -1, + &list_kinetic_scroll_press_default, "kinetic scroll press", + list_kinetic_load_from_cfg, list_kinetic_write_to_cfg, + list_kinetic_is_default, list_kinetic_set_default), #endif OFFON_SETTING(0, prevent_skip, LANG_PREVENT_SKIPPING, false, "prevent track skip", NULL), OFFON_SETTING(0, rewind_across_tracks, LANG_REWIND_ACROSS_TRACKS, false, "rewind across tracks", NULL), -- rockbox-cvs mailing list [email protected] https://lists.haxx.se/mailman/listinfo/rockbox-cvs