[Dillo-dev] [PATCH] Implement kinetic scrolling for touchscreens

4 views
Skip to first unread message

Alyssa Rosenzweig

unread,
Jun 11, 2018, 12:56:51 AM6/11/18
to dill...@dillo.org
This patch may depend on the touchscreen drag patch from this morning.

This patch implements "kinetic scrolling", where the page continues to
scroll for a moment after the user lifts their finger, simulating
inertia and providing a more fluid experience. Users -- or at least I :)
-- expect this for a touchscreen interface, in accordance with other
touchscreen-aware browsers.

It employs a simple quadratic falloff curve, modeled after
constant-acceleration motion, where acceleration is set to -k*initial
velocity for some constant k. THe value of k determines the speed of the
falloff. The curve still feels a little "wonky" compared to e.g.
Firefox, but it's alright for now.

The timer's frame rate is dynamically scaled based on the amount of
motion, in order to conserve resources due to excess scrolling of small
amounts.

The functionality is exposed as a configuration option (default off); no
regressions are expected.

---

diff -r decebf1a6a78 dillorc
--- a/dillorc Sun Jun 10 20:07:07 2018 +0000
+++ b/dillorc Mon Jun 11 04:50:40 2018 +0000
@@ -387,6 +387,13 @@
# disabled; to select text, hold the shift-key and select as usual.
#touchscreen_drags_page=NO

+# Drag-based scrolling uses inertia-based "kinetic scrolling" When scrolling
+# with a touchscreen, kinetic scrolling enables a more fluid experience, where
+# the page continues to scroll after users release their finger, gradually
+# coming to a stop. If using Dillo on a tablet or other touchscreen, set it to
+# YES. Otherwise, set it to NO.
+#kinetic_scrolling=NO
+
# Focus follows new Tabs.
# You can hold SHIFT to temporarily revert this behaviour.
#focus_new_tab=YES
diff -r decebf1a6a78 dw/fltkviewport.cc
--- a/dw/fltkviewport.cc Sun Jun 10 20:07:07 2018 +0000
+++ b/dw/fltkviewport.cc Mon Jun 11 04:50:40 2018 +0000
@@ -292,9 +292,39 @@
if (Fl::event_inside(this))
Fl::remove_timeout(selectionScroll);
if (dragScrolling) {
- scroll(dragX - Fl::event_x(), dragY - Fl::event_y());
+ int dx = dragX - Fl::event_x();
+ int dy = dragY - Fl::event_y();
+
+ if (hasKineticScroll) {
+ /* Technically, kinetic velocity is <dx, dy> / d_time, rather
+ * than merely <dx, dy>. However, this division
+ * (multiplication by a constant) does not need to occur every
+ * event; it instead happens once at the end in the RELEASE
+ * handler below.
+ *
+ * Also, if the component is already set but we're scrolling
+ * with a zero delta, this is probably a spurious event.
+ * Particularly if this occurs right before release, this
+ * means that velocity will be calculated as (essentially)
+ * 0/0. Just ignore setting these events entirely. */
+
+ if (kinetic_v0_x == 0.0 || dx)
+ kinetic_v0_x = (float) dx;
+
+ if (kinetic_v0_y == 0.0 || dy)
+ kinetic_v0_y = (float) dy;
+
+ /* Swap time queue and get new time for calculating event time
+ * deltas */
+
+ last_drag_time_2 = last_drag_time;
+ clock_gettime(CLOCK_MONOTONIC, &last_drag_time);
+ }
+
+ scroll(dx, dy);
dragX = Fl::event_x();
dragY = Fl::event_y();
+
return 1;
} else if (verScrolling) {
vscrollbar->handle(event);
@@ -323,6 +353,28 @@
} else if (horScrolling) {
ret = hscrollbar->handle(event);
}
+
+ /* For kinetic scrolling, release is only the start (inertia). Set up a
+ * timer to continue the scroll */
+ if (dragScrolling && hasKineticScroll
+ && !Fl::has_timeout(kineticScroll, this)) {
+ /* Start the clock... now! */
+ clock_gettime(CLOCK_MONOTONIC, &first_kinetic_time);
+
+ /* As a delta from start -- zero by definition at the beginning */
+ last_kinetic_time = 0.0f;
+
+ /* Convert units from "pixels per delta" to "pixels per second" as
+ * expected by the kinetic scrolling algorithm, by dividing by the
+ * delta time between events in seconds */
+
+ float drag_time_diff = timediff(last_drag_time, last_drag_time_2);
+ kinetic_v0_x /= drag_time_diff;
+ kinetic_v0_y /= drag_time_diff;
+
+ Fl::add_timeout(0.025, kineticScroll, this);
+ }
+
horScrolling = verScrolling = dragScrolling = 0;
break;

@@ -474,6 +526,84 @@
Fl::repeat_timeout(0.025, selectionScroll, data);
}

+/*
+ * Kinetic scrolling inertia implementation
+ */
+float FltkViewport::kineticScroll ()
+{
+ /* Get current time */
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ /* Calculate time relative to start, calculate delta, and update clock */
+ float time = timediff(now, first_kinetic_time);
+ float delta = time - last_kinetic_time;
+ last_kinetic_time = time;
+
+ /* Calculate componetwise difference. Acceleration is defined to be a scalar
+ * multiple of the initial velocity, negative to indicate slowing down. */
+
+ float value = (1.0 - 0.5*(2*time - delta));
+
+ /* Split up velocity / position changes to avoid division by delta later */
+
+ float vx = kinetic_v0_x * value;
+ float vy = kinetic_v0_y * value;
+
+ float dx = delta * vx;
+ float dy = delta * vy;
+
+ /* If we've switched directions for some reason, cease and desist scrolling
+ * that axis */
+
+ if (!((dx < 0 && kinetic_v0_x < 0) || (dx > 0 && kinetic_v0_x > 0))) {
+ dx = 0;
+ kinetic_v0_x = 0;
+ }
+
+ if (!((dy < 0 && kinetic_v0_y < 0) || (dy > 0 && kinetic_v0_y > 0))) {
+ dy = 0;
+ kinetic_v0_y = 0;
+ }
+
+ /* Actual scroll */
+ scroll ((int) dx, (int) dy);
+
+ /* Return scroll speed (squared) */
+ return vx*vx + vy*vy;
+}
+
+void FltkViewport::kineticScroll (void *data)
+{
+ /* Run kinetic scroll algorithm, which returns scroll speed (squared) */
+ float speed = ((FltkViewport *)data)->kineticScroll ();
+
+ /* If the speed is too slow, cancel the timeout to conserve resources. */
+ if (speed < 9.0)
+ return;
+
+ /* Otherwise, continue the loop. The FPS is adjusted depending on the speed
+ * of scrolling. Slower scrolling -> lower FPS. The goal of this curve is to
+ * minimise CPU usage from scrolling with minimal visible degradation of
+ * output (specifically, CPU time spent in the Xorg process on a system
+ * without GPU acceleration, etc). This curve was experimentally determined
+ * based on the form of a rational function. Better curves almost certainly
+ * exist. */
+
+ float min_fps = 25.0f;
+ float max_fps = 35.0f;
+
+ /* TODO: Determine this constant theoretically, rather than what
+ * experimentally "feels right", to avoid certain experimental biases (e.g.
+ * the resoltuion of the monitor I used) */
+ float fps_k = 0.0000005f;
+
+ float fps = min_fps + ((max_fps - min_fps) * (1.0 - (1.0 / (1.0 + fps_k*speed))));
+
+ /* Set timer for next iteration of the loop */
+ Fl::repeat_timeout(1.0 / fps, kineticScroll, data);
+}
+
void FltkViewport::setViewportSize (int width, int height,
int hScrollbarThickness,
int vScrollbarThickness)
diff -r decebf1a6a78 dw/fltkviewport.hh
--- a/dw/fltkviewport.hh Sun Jun 10 20:07:07 2018 +0000
+++ b/dw/fltkviewport.hh Mon Jun 11 04:50:40 2018 +0000
@@ -43,6 +43,22 @@
void selectionScroll();
static void selectionScroll(void *vport);

+ int hasKineticScroll;
+ struct timespec last_drag_time;
+ struct timespec last_drag_time_2;
+ float last_kinetic_time;
+ struct timespec first_kinetic_time;
+ float kinetic_v0_x, kinetic_v0_y;
+ float kineticScroll();
+ static void kineticScroll(void *vport);
+
+ /* Used for kinetic scrolling computations */
+ static inline float timediff(struct timespec now, struct timespec then) {
+ time_t sec_diff = now.tv_sec - then.tv_sec;
+ long nano_diff = now.tv_nsec - then.tv_nsec;
+ return ((float) sec_diff) + (((double) nano_diff) / 1000000000.0f);
+ }
+
void updateCanvasWidgets (int oldScrollX, int oldScrollY);
static void draw_area (void *data, int x, int y, int w, int h);

@@ -77,6 +93,7 @@
void setShowScrollbar (bool enable) { showScrollbar = enable; }
void setDragScroll (bool enable) { hasDragScroll = enable ? 1 : 0; }
void setTouchScroll (bool enable) { hasTouchScroll = enable ? 1 : 0; }
+ void setKineticScroll (bool enable) { hasKineticScroll = enable ? 1 : 0; }
void addGadget (Fl_Widget *gadget);
};

diff -r decebf1a6a78 src/prefs.c
--- a/src/prefs.c Sun Jun 10 20:07:07 2018 +0000
+++ b/src/prefs.c Mon Jun 11 04:50:40 2018 +0000
@@ -76,6 +76,7 @@
prefs.load_stylesheets=TRUE;
prefs.middle_click_drags_page = TRUE;
prefs.touchscreen_drags_page = FALSE;
+ prefs.kinetic_scroll = FALSE;
prefs.middle_click_opens_new_tab = TRUE;
prefs.right_click_closes_tab = FALSE;
prefs.no_proxy = dStrdup(PREFS_NO_PROXY);
diff -r decebf1a6a78 src/prefs.h
--- a/src/prefs.h Sun Jun 10 20:07:07 2018 +0000
+++ b/src/prefs.h Mon Jun 11 04:50:40 2018 +0000
@@ -111,6 +111,7 @@
bool_t show_extra_warnings;
bool_t middle_click_drags_page;
bool_t touchscreen_drags_page;
+ bool_t kinetic_scroll;
int penalty_hyphen, penalty_hyphen_2;
int penalty_em_dash_left, penalty_em_dash_right, penalty_em_dash_right_2;
int stretchability_factor;
diff -r decebf1a6a78 src/prefsparser.cc
--- a/src/prefsparser.cc Sun Jun 10 20:07:07 2018 +0000
+++ b/src/prefsparser.cc Mon Jun 11 04:50:40 2018 +0000
@@ -184,6 +184,7 @@
PREFS_BOOL, 0 },
{ "touchscreen_drags_page", &prefs.touchscreen_drags_page,
PREFS_BOOL, 0 },
+ { "kinetic_scrolling", &prefs.kinetic_scroll, PREFS_BOOL, 0 },
{ "middle_click_opens_new_tab", &prefs.middle_click_opens_new_tab,
PREFS_BOOL, 0 },
{ "right_click_closes_tab", &prefs.right_click_closes_tab, PREFS_BOOL, 0 },
diff -r decebf1a6a78 src/uicmd.cc
--- a/src/uicmd.cc Sun Jun 10 20:07:07 2018 +0000
+++ b/src/uicmd.cc Mon Jun 11 04:50:40 2018 +0000
@@ -607,6 +607,7 @@
viewport->setShowScrollbar (prefs.show_scrollbar);
viewport->setDragScroll (prefs.middle_click_drags_page);
viewport->setTouchScroll (prefs.touchscreen_drags_page);
+ viewport->setKineticScroll (prefs.kinetic_scroll);
layout->setTouchScroll (prefs.touchscreen_drags_page);
layout->attachView (viewport);
new_ui->set_render_layout(viewport);

_______________________________________________
Dillo-dev mailing list
Dill...@dillo.org
http://lists.dillo.org/cgi-bin/mailman/listinfo/dillo-dev

Alyssa Rosenzweig

unread,
Jun 11, 2018, 1:23:21 AM6/11/18
to dill...@dillo.org
Oh, I had forgot to on the earlier patches (plus I didn't want to bloat
the diff), but if merged, please add my name to the respective
copyright/authors file(s) :)

Nick Warne

unread,
Jun 11, 2018, 2:31:37 PM6/11/18
to dill...@dillo.org, Alyssa Rosenzweig
Good stuff Alyssa.

I haven't used this patch yet as I am very rarely on my computer these days (smart phones, eh!), but the work you have done looks great.

Nick

Reply all
Reply to author
Forward
0 new messages