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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.