From 689b873411a6b5e3ed7dc67569feee62e479c5de Mon Sep 17 00:00:00 2001 From: Alexander Mikhaylenko Date: Thu, 4 Feb 2021 19:50:13 +0500 Subject: [PATCH 1/6] swipe-tracker: Calculate velocity using scroll history In some cases we may get event with very low delta at the end of the swipe. While we can't completely ignore them, we can smooth them using a scroll history, similarly to what GTK kinetic scrolling does: keep track of the last 150ms of events, and sum their deltas when calculating the velocity. --- src/adw-swipe-tracker.c | 134 ++++++++++++++++++++++++++++++---------- 1 file changed, 103 insertions(+), 31 deletions(-) diff --git a/src/adw-swipe-tracker.c b/src/adw-swipe-tracker.c index 64a2e34ab..4d7c0a6a1 100644 --- a/src/adw-swipe-tracker.c +++ b/src/adw-swipe-tracker.c @@ -14,6 +14,7 @@ #define TOUCHPAD_BASE_DISTANCE_H 400 #define TOUCHPAD_BASE_DISTANCE_V 300 +#define EVENT_HISTORY_THRESHOLD_MS 150 #define SCROLL_MULTIPLIER 10 #define MIN_ANIMATION_DURATION 100 #define MAX_ANIMATION_DURATION 400 @@ -47,6 +48,11 @@ typedef enum { ADW_SWIPE_TRACKER_STATE_REJECTED, } AdwSwipeTrackerState; +typedef struct { + gdouble delta; + guint32 time; +} EventHistoryRecord; + struct _AdwSwipeTracker { GObject parent_instance; @@ -60,13 +66,12 @@ struct _AdwSwipeTracker gdouble pointer_x; gdouble pointer_y; + GArray *event_history; + gdouble start_x; gdouble start_y; gboolean use_capture_phase; - guint32 prev_time; - gdouble velocity; - gdouble initial_progress; gdouble progress; gboolean cancelled; @@ -117,13 +122,12 @@ reset (AdwSwipeTracker *self) self->initial_progress = 0; self->progress = 0; + g_array_remove_range (self->event_history, 0, self->event_history->len); + self->start_x = 0; self->start_y = 0; self->use_capture_phase = FALSE; - self->prev_time = 0; - self->velocity = 0; - self->cancelled = FALSE; } @@ -166,18 +170,74 @@ gesture_prepare (AdwSwipeTracker *self, self->initial_progress = adw_swipeable_get_progress (self->swipeable); self->progress = self->initial_progress; - self->velocity = 0; self->state = ADW_SWIPE_TRACKER_STATE_PENDING; } static void -gesture_begin (AdwSwipeTracker *self, - guint32 time) +trim_history (AdwSwipeTracker *self, + guint32 current_time) +{ + guint32 threshold_time = current_time - EVENT_HISTORY_THRESHOLD_MS; + guint i; + + for (i = 0; i < self->event_history->len; i++) { + guint32 time = g_array_index (self->event_history, + EventHistoryRecord, i).time; + + if (time >= threshold_time) + break; + } + + if (i > 0) + g_array_remove_range (self->event_history, 0, i); +} + +static void +append_to_history (AdwSwipeTracker *self, + guint32 time, + gdouble delta) +{ + EventHistoryRecord record; + + trim_history (self, time); + + record.delta = delta; + record.time = time; + + g_array_append_val (self->event_history, record); +} + +static gdouble +calculate_velocity (AdwSwipeTracker *self) +{ + gdouble total_delta = 0; + guint32 first_time = 0, last_time = 0; + guint i; + + for (i = 0; i < self->event_history->len; i++) { + EventHistoryRecord *r = + &g_array_index (self->event_history, EventHistoryRecord, i); + + if (i == 0) + first_time = r->time; + else + total_delta += r->delta; + + last_time = r->time; + } + + if (first_time == last_time) + return 0; + + return total_delta / (last_time - first_time); +} + +static void +gesture_begin (AdwSwipeTracker *self) { if (self->state != ADW_SWIPE_TRACKER_STATE_PENDING) return; - self->prev_time = time; self->state = ADW_SWIPE_TRACKER_STATE_SCROLLING; } @@ -192,12 +252,12 @@ gesture_update (AdwSwipeTracker *self, if (self->state != ADW_SWIPE_TRACKER_STATE_SCROLLING) return; - if (time != self->prev_time) - self->velocity = delta / (time - self->prev_time); + append_to_history (self, time, delta); get_range (self, &first_point, &last_point); progress = self->progress + delta; + progress = CLAMP (progress, first_point, last_point); /* FIXME: this is a hack to prevent swiping more than 1 page at once */ @@ -206,8 +266,6 @@ gesture_update (AdwSwipeTracker *self, self->progress = progress; adw_swipe_tracker_emit_update_swipe (self, progress); - - self->prev_time = time; } static void @@ -242,7 +300,8 @@ get_closest_snap_points (AdwSwipeTracker *self, static gdouble get_end_progress (AdwSwipeTracker *self, - gdouble distance) + gdouble distance, + gdouble velocity) { gdouble upper, lower, middle; @@ -253,16 +312,17 @@ get_end_progress (AdwSwipeTracker *self, middle = (upper + lower) / 2; if (self->progress > middle) - return (self->velocity * distance > -VELOCITY_THRESHOLD || + return (velocity * distance > -VELOCITY_THRESHOLD || self->initial_progress > upper) ? upper : lower; - return (self->velocity * distance < VELOCITY_THRESHOLD || + return (velocity * distance < VELOCITY_THRESHOLD || self->initial_progress < lower) ? lower : upper; } static void gesture_end (AdwSwipeTracker *self, - gdouble distance) + gdouble distance, + guint32 time) { gdouble end_progress, velocity; gint64 duration; @@ -270,11 +330,14 @@ gesture_end (AdwSwipeTracker *self, if (self->state == ADW_SWIPE_TRACKER_STATE_NONE) return; - end_progress = get_end_progress (self, distance); + trim_history (self, time); + + velocity = calculate_velocity (self); + + end_progress = get_end_progress (self, distance, velocity); - velocity = ANIMATION_BASE_VELOCITY; - if ((end_progress - self->progress) * self->velocity > 0) - velocity = self->velocity; + if ((end_progress - self->progress) * velocity <= 0) + velocity = ANIMATION_BASE_VELOCITY; duration = ABS ((self->progress - end_progress) / velocity * DURATION_MULTIPLIER); if (self->progress != end_progress) @@ -290,7 +353,8 @@ gesture_end (AdwSwipeTracker *self, static void gesture_cancel (AdwSwipeTracker *self, - gdouble distance) + gdouble distance, + guint32 time) { if (self->state != ADW_SWIPE_TRACKER_STATE_PENDING && self->state != ADW_SWIPE_TRACKER_STATE_SCROLLING) { @@ -300,7 +364,7 @@ gesture_cancel (AdwSwipeTracker *self, } self->cancelled = TRUE; - gesture_end (self, distance); + gesture_end (self, distance, time); } static gboolean @@ -468,7 +532,7 @@ drag_update_cb (AdwSwipeTracker *self, if (drag_distance >= DRAG_THRESHOLD_DISTANCE) { if ((is_vertical == is_offset_vertical) && !is_overshooting) { - gesture_begin (self, time); + gesture_begin (self); self->prev_offset = offset; gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); } else { @@ -490,6 +554,7 @@ drag_end_cb (AdwSwipeTracker *self, GtkGestureDrag *gesture) { gdouble distance; + guint32 time; distance = adw_swipeable_get_distance (self->swipeable); @@ -500,13 +565,15 @@ drag_end_cb (AdwSwipeTracker *self, return; } + time = gtk_event_controller_get_current_event_time (GTK_EVENT_CONTROLLER (gesture)); + if (self->state != ADW_SWIPE_TRACKER_STATE_SCROLLING) { - gesture_cancel (self, distance); + gesture_cancel (self, distance, time); gtk_gesture_set_state (self->touch_gesture, GTK_EVENT_SEQUENCE_DENIED); return; } - gesture_end (self, distance); + gesture_end (self, distance, time); } static void @@ -514,11 +581,14 @@ drag_cancel_cb (AdwSwipeTracker *self, GdkEventSequence *sequence, GtkGesture *gesture) { + guint32 time; gdouble distance; distance = adw_swipeable_get_distance (self->swipeable); - gesture_cancel (self, distance); + time = gtk_event_controller_get_current_event_time (GTK_EVENT_CONTROLLER (gesture)); + + gesture_cancel (self, distance, time); gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED); } @@ -577,14 +647,14 @@ handle_scroll_event (AdwSwipeTracker *self, (delta > 0 && self->progress >= last_point); if (!is_overshooting) - gesture_begin (self, time); + gesture_begin (self); else - gesture_cancel (self, distance); + gesture_cancel (self, distance, time); } if (self->state == ADW_SWIPE_TRACKER_STATE_SCROLLING) { if (gdk_scroll_event_is_stop (event)) { - gesture_end (self, distance); + gesture_end (self, distance, time); } else { gesture_update (self, delta / distance * SCROLL_MULTIPLIER, time); return GDK_EVENT_STOP; @@ -982,7 +1052,9 @@ adw_swipe_tracker_class_init (AdwSwipeTrackerClass *klass) static void adw_swipe_tracker_init (AdwSwipeTracker *self) { + self->event_history = g_array_new (FALSE, FALSE, sizeof (EventHistoryRecord)); reset (self); + self->orientation = GTK_ORIENTATION_HORIZONTAL; self->enabled = TRUE; } -- GitLab From fff1ad514611d717da0a2d3f4869ee7ca1ca03ad Mon Sep 17 00:00:00 2001 From: Alexander Mikhaylenko Date: Thu, 4 Feb 2021 21:48:28 +0500 Subject: [PATCH 2/6] swipe-tracker: Rework end point calculation Previously we used a bunch of heuristics for this. We checked if velocity was directed towards the nearest snap point and its value was larger than a threshold. If it is, we completed the swipe, otherwise we cancelled it. This was good enough at the time, because this code was originally written for back/forward swipe. Since then, the swipe tracker was extended to handle arbitrary snap points and not just 0 and 1, or -1 and 0, depending on text direction. After that it was iterated on, but never significantly redone. This worked well enough, but had two problems: 1. In some cases, notably for AdwCarousel with non-expanded pages, it may be wanted to be able to swipe through multiple pages at once. This wasn't really possible because we always picked the adjacent snap point. 2. Since we can't do that well, we want to restrict swipes to one page at a time. It was done in a rather hacky way by clamping the position into [-1, 1] range from the place where we started the swipe. This works if we start the swipe from idle position, but if an animation was already going, the range would be clamped to arbitrary values, and very likely containing only one snap point, which we already swiped past at this point. In this case, finishing the swipe would cancel it regardless of velocity. This means that if one tries to quickly move through carousel pages via swiping, half of the swipes will be inexplicably cancelled. We'll use the deceleration formula from https://medium.com/@esskeetit/how-uiscrollview-works-e418adc47060#10ce to calculate then projection point, then pick the nearest snap point and calculate the duration as we did before. It works well enough for short distances, but has two problems: 1. It caps the maximum distance at a pretty low value - about 5 pages in my testing. 2. With how we pick the nearest snap point, it's too easy to accidentally cancel the swipe, To combat the first problem, we can modify the curve: only use linear function at small distances, and smoothly transition it to a parabola further. For the second problem we can add two special cases: first, if the swipe ended up between the initial snap point and the next one, we always prefer the latter. Second, a good old velocity threshold for cancelling. We'll also use a slightly smaller deceleration value for touchpad: 0.997 instead of 0.998. Now that we can pick any snap point, the [-1, 1] clamping doesn't make sense anymore, so instead let's replace it with a more flexible mechanism: if we're near a snap point, pick its adjacent snap points. Otherwise, take the two nearest snap points, and take their adjacent snap points. This way we have 3 snap points to choose from when starting a swipe from an idle position, and 4 if we start during an ongoing transition. This way, if we've just swiped from snap point n to n+1, the transition will pick snap points n-1, n, n+1, n+2 and if we swipe again, we will likely land on n+2. During that transition, if we swipe again, it will likely have already passed the snap point n+1, so this time the available snap points will be n, n+1, n+2, n+3, so we can swipe again and it will still complete, and so on. This will make it easy to allow multi-page swipes as well, by just removing the clamping. --- src/adw-swipe-tracker.c | 231 +++++++++++++++++++++++++++------------- 1 file changed, 159 insertions(+), 72 deletions(-) diff --git a/src/adw-swipe-tracker.c b/src/adw-swipe-tracker.c index 4d7c0a6a1..396c8b2af 100644 --- a/src/adw-swipe-tracker.c +++ b/src/adw-swipe-tracker.c @@ -18,10 +18,18 @@ #define SCROLL_MULTIPLIER 10 #define MIN_ANIMATION_DURATION 100 #define MAX_ANIMATION_DURATION 400 -#define VELOCITY_THRESHOLD 0.4 +#define VELOCITY_THRESHOLD_TOUCH 0.3 +#define VELOCITY_THRESHOLD_TOUCHPAD 0.6 +#define DECELERATION_TOUCH 0.998 +#define DECELERATION_TOUCHPAD 0.997 +#define VELOCITY_CURVE_THRESHOLD 2 +#define DECELERATION_PARABOLA_MULTIPLIER 0.35 #define DURATION_MULTIPLIER 3 #define ANIMATION_BASE_VELOCITY 0.002 #define DRAG_THRESHOLD_DISTANCE 16 +#define EPSILON 0.005 + +#define SIGN(x) ((x) > 0.0 ? 1.0 : ((x) < 0.0 ? -1.0 : 0.0)) /** * SECTION:adw-swipe-tracker @@ -194,8 +202,8 @@ trim_history (AdwSwipeTracker *self, static void append_to_history (AdwSwipeTracker *self, - guint32 time, - gdouble delta) + gdouble delta, + guint32 time) { EventHistoryRecord record; @@ -241,91 +249,161 @@ gesture_begin (AdwSwipeTracker *self) self->state = ADW_SWIPE_TRACKER_STATE_SCROLLING; } -static void -gesture_update (AdwSwipeTracker *self, - gdouble delta, - guint32 time) +static gint +find_closest_point (gdouble *points, + gint n, + gdouble pos) { - gdouble progress; - gdouble first_point, last_point; + guint i, min = 0; - if (self->state != ADW_SWIPE_TRACKER_STATE_SCROLLING) - return; + for (i = 1; i < n; i++) + if (ABS (points[i] - pos) < ABS (points[min] - pos)) + min = i; - append_to_history (self, time, delta); + return min; +} - get_range (self, &first_point, &last_point); +static gint +find_next_point (gdouble *points, + gint n, + gdouble pos) +{ + guint i; - progress = self->progress + delta; + for (i = 0; i < n; i++) + if (points[i] >= pos) + return i; - progress = CLAMP (progress, first_point, last_point); + return -1; +} - /* FIXME: this is a hack to prevent swiping more than 1 page at once */ - progress = CLAMP (progress, self->initial_progress - 1, self->initial_progress + 1); +static gint +find_previous_point (gdouble *points, + gint n, + gdouble pos) +{ + gint i; - self->progress = progress; + for (i = n - 1; i >= 0; i--) + if (points[i] <= pos) + return i; - adw_swipe_tracker_emit_update_swipe (self, progress); + return -1; +} + +static gint +find_point_for_projection (AdwSwipeTracker *self, + gdouble *points, + gint n, + gdouble pos, + gdouble velocity) +{ + gint initial = find_closest_point (points, n, self->initial_progress); + gint prev = find_previous_point (points, n, pos); + gint next = find_next_point (points, n, pos); + + if ((velocity > 0 ? prev : next) == initial) + return velocity > 0 ? next : prev; + + return find_closest_point (points, n, pos); } static void -get_closest_snap_points (AdwSwipeTracker *self, - gdouble *upper, - gdouble *lower) +get_bounds (AdwSwipeTracker *self, + gdouble *points, + gint n, + gdouble pos, + gdouble *lower, + gdouble *upper) { - gint i, n; - gdouble *points; + gint prev, next; + gint closest = find_closest_point (points, n, self->initial_progress); + + if (ABS (points[closest] - self->initial_progress) < EPSILON) { + prev = next = closest; + } else { + prev = find_previous_point (points, n, self->initial_progress); + next = find_next_point (points, n, self->initial_progress); + } - *upper = 0; - *lower = 0; + *lower = points[MAX (prev - 1, 0)]; + *upper = points[MIN (next + 1, n - 1)]; +} + +static void +gesture_update (AdwSwipeTracker *self, + gdouble delta, + guint32 time) +{ + gdouble lower, upper; + gdouble progress; + g_autofree gdouble *points = NULL; + gint n; + + if (self->state != ADW_SWIPE_TRACKER_STATE_SCROLLING) + return; points = adw_swipeable_get_snap_points (self->swipeable, &n); + get_bounds (self, points, n, self->initial_progress, &lower, &upper); - for (i = 0; i < n; i++) { - if (points[i] >= self->progress) { - *upper = points[i]; - break; - } - } + progress = self->progress + delta; + progress = CLAMP (progress, lower, upper); - for (i = n - 1; i >= 0; i--) { - if (points[i] <= self->progress) { - *lower = points[i]; - break; - } - } + self->progress = progress; - g_free (points); + adw_swipe_tracker_emit_update_swipe (self, progress); } static gdouble get_end_progress (AdwSwipeTracker *self, - gdouble distance, - gdouble velocity) + gdouble velocity, + gboolean is_touchpad) { - gdouble upper, lower, middle; + gdouble pos, decel, slope, lower, upper; + g_autofree gdouble *points = NULL; + gint n; if (self->cancelled) return adw_swipeable_get_cancel_progress (self->swipeable); - get_closest_snap_points (self, &upper, &lower); - middle = (upper + lower) / 2; + points = adw_swipeable_get_snap_points (self->swipeable, &n); + + if (ABS (velocity) < (is_touchpad ? VELOCITY_THRESHOLD_TOUCHPAD : VELOCITY_THRESHOLD_TOUCH)) + return points[find_closest_point (points, n, self->progress)]; + + decel = is_touchpad ? DECELERATION_TOUCHPAD : DECELERATION_TOUCH; + slope = decel / (1.0 - decel) / 1000.0; + + if (ABS (velocity) > VELOCITY_CURVE_THRESHOLD) { + const gdouble c = slope / 2 / DECELERATION_PARABOLA_MULTIPLIER; + const gdouble x = ABS (velocity) - VELOCITY_CURVE_THRESHOLD + c; + + pos = DECELERATION_PARABOLA_MULTIPLIER * x * x + - DECELERATION_PARABOLA_MULTIPLIER * c * c + + slope * VELOCITY_CURVE_THRESHOLD; + } else { + pos = ABS (velocity) * slope; + } + + pos = (pos * SIGN (velocity)) + self->progress; - if (self->progress > middle) - return (velocity * distance > -VELOCITY_THRESHOLD || - self->initial_progress > upper) ? upper : lower; + get_bounds (self, points, n, self->initial_progress, &lower, &upper); - return (velocity * distance < VELOCITY_THRESHOLD || - self->initial_progress < lower) ? lower : upper; + pos = CLAMP (pos, lower, upper); + + pos = points[find_point_for_projection (self, points, n, pos, velocity)]; + + return pos; } static void gesture_end (AdwSwipeTracker *self, gdouble distance, - guint32 time) + guint32 time, + gboolean is_touchpad) { gdouble end_progress, velocity; - gint64 duration; + gint64 duration, max_duration; if (self->state == ADW_SWIPE_TRACKER_STATE_NONE) return; @@ -334,14 +412,18 @@ gesture_end (AdwSwipeTracker *self, velocity = calculate_velocity (self); - end_progress = get_end_progress (self, distance, velocity); + end_progress = get_end_progress (self, velocity, is_touchpad); + + velocity /= distance; if ((end_progress - self->progress) * velocity <= 0) velocity = ANIMATION_BASE_VELOCITY; + max_duration = MAX_ANIMATION_DURATION * log2 (1 + MAX (1, ceil (ABS (self->progress - end_progress)))); + duration = ABS ((self->progress - end_progress) / velocity * DURATION_MULTIPLIER); if (self->progress != end_progress) - duration = CLAMP (duration, MIN_ANIMATION_DURATION, MAX_ANIMATION_DURATION); + duration = CLAMP (duration, MIN_ANIMATION_DURATION, max_duration); adw_swipe_tracker_emit_end_swipe (self, duration, end_progress); @@ -354,7 +436,8 @@ gesture_end (AdwSwipeTracker *self, static void gesture_cancel (AdwSwipeTracker *self, gdouble distance, - guint32 time) + guint32 time, + gboolean is_touchpad) { if (self->state != ADW_SWIPE_TRACKER_STATE_PENDING && self->state != ADW_SWIPE_TRACKER_STATE_SCROLLING) { @@ -364,7 +447,7 @@ gesture_cancel (AdwSwipeTracker *self, } self->cancelled = TRUE; - gesture_end (self, distance, time); + gesture_end (self, distance, time, is_touchpad); } static gboolean @@ -487,21 +570,21 @@ drag_update_cb (AdwSwipeTracker *self, gdouble offset_y, GtkGestureDrag *gesture) { - gdouble offset, distance; + gdouble offset, distance, delta; gboolean is_vertical, is_offset_vertical; guint32 time; distance = adw_swipeable_get_distance (self->swipeable); is_vertical = (self->orientation == GTK_ORIENTATION_VERTICAL); - if (is_vertical) - offset = -offset_y / distance; - else - offset = -offset_x / distance; + offset = is_vertical ? offset_y : offset_x; - if (self->reversed) + if (!self->reversed) offset = -offset; + delta = offset - self->prev_offset; + self->prev_offset = offset; + is_offset_vertical = (ABS (offset_y) > ABS (offset_x)); if (self->state == ADW_SWIPE_TRACKER_STATE_REJECTED) { @@ -509,6 +592,10 @@ drag_update_cb (AdwSwipeTracker *self, return; } + time = gtk_event_controller_get_current_event_time (GTK_EVENT_CONTROLLER (gesture)); + + append_to_history (self, delta, time); + if (self->state == ADW_SWIPE_TRACKER_STATE_NONE) { if (is_vertical == is_offset_vertical) gesture_prepare (self, offset > 0 ? ADW_NAVIGATION_DIRECTION_FORWARD : ADW_NAVIGATION_DIRECTION_BACK, TRUE); @@ -517,8 +604,6 @@ drag_update_cb (AdwSwipeTracker *self, return; } - time = gtk_event_controller_get_current_event_time (GTK_EVENT_CONTROLLER (gesture)); - if (self->state == ADW_SWIPE_TRACKER_STATE_PENDING) { gdouble drag_distance; gdouble first_point, last_point; @@ -541,10 +626,8 @@ drag_update_cb (AdwSwipeTracker *self, } } - if (self->state == ADW_SWIPE_TRACKER_STATE_SCROLLING) { - gesture_update (self, offset - self->prev_offset, time); - self->prev_offset = offset; - } + if (self->state == ADW_SWIPE_TRACKER_STATE_SCROLLING) + gesture_update (self, delta / distance, time); } static void @@ -568,12 +651,12 @@ drag_end_cb (AdwSwipeTracker *self, time = gtk_event_controller_get_current_event_time (GTK_EVENT_CONTROLLER (gesture)); if (self->state != ADW_SWIPE_TRACKER_STATE_SCROLLING) { - gesture_cancel (self, distance, time); + gesture_cancel (self, distance, time, FALSE); gtk_gesture_set_state (self->touch_gesture, GTK_EVENT_SEQUENCE_DENIED); return; } - gesture_end (self, distance, time); + gesture_end (self, distance, time, FALSE); } static void @@ -588,7 +671,7 @@ drag_cancel_cb (AdwSwipeTracker *self, time = gtk_event_controller_get_current_event_time (GTK_EVENT_CONTROLLER (gesture)); - gesture_cancel (self, distance, time); + gesture_cancel (self, distance, time, FALSE); gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED); } @@ -646,16 +729,20 @@ handle_scroll_event (AdwSwipeTracker *self, is_overshooting = (delta < 0 && self->progress <= first_point) || (delta > 0 && self->progress >= last_point); + append_to_history (self, delta * SCROLL_MULTIPLIER, time); + if (!is_overshooting) gesture_begin (self); else - gesture_cancel (self, distance, time); + gesture_cancel (self, distance, time, TRUE); } if (self->state == ADW_SWIPE_TRACKER_STATE_SCROLLING) { if (gdk_scroll_event_is_stop (event)) { - gesture_end (self, distance, time); + gesture_end (self, distance, time, TRUE); } else { + append_to_history (self, delta * SCROLL_MULTIPLIER, time); + gesture_update (self, delta / distance * SCROLL_MULTIPLIER, time); return GDK_EVENT_STOP; } -- GitLab From 45833931738c3b24da143213959e843cf83557a8 Mon Sep 17 00:00:00 2001 From: Alexander Mikhaylenko Date: Thu, 9 Jul 2020 19:31:13 +0500 Subject: [PATCH 3/6] swipe-tracker: Add allow-long-swipes property Since we now have the ability to support swiping through multiple pages, expose it as a property. --- src/adw-swipe-tracker.c | 95 +++++++++++++++++++++++++++++++++++++---- src/adw-swipe-tracker.h | 6 +++ 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/src/adw-swipe-tracker.c b/src/adw-swipe-tracker.c index 396c8b2af..5258bf68b 100644 --- a/src/adw-swipe-tracker.c +++ b/src/adw-swipe-tracker.c @@ -69,6 +69,7 @@ struct _AdwSwipeTracker gboolean enabled; gboolean reversed; gboolean allow_mouse_drag; + gboolean allow_long_swipes; GtkOrientation orientation; gdouble pointer_x; @@ -103,10 +104,11 @@ enum { PROP_ENABLED, PROP_REVERSED, PROP_ALLOW_MOUSE_DRAG, + PROP_ALLOW_LONG_SWIPES, /* GtkOrientable */ PROP_ORIENTATION, - LAST_PROP = PROP_ALLOW_MOUSE_DRAG + 1, + LAST_PROP = PROP_ALLOW_LONG_SWIPES + 1, }; static GParamSpec *props[LAST_PROP]; @@ -337,14 +339,19 @@ gesture_update (AdwSwipeTracker *self, { gdouble lower, upper; gdouble progress; - g_autofree gdouble *points = NULL; - gint n; if (self->state != ADW_SWIPE_TRACKER_STATE_SCROLLING) return; - points = adw_swipeable_get_snap_points (self->swipeable, &n); - get_bounds (self, points, n, self->initial_progress, &lower, &upper); + if (!self->allow_long_swipes) { + g_autofree gdouble *points = NULL; + gint n; + + points = adw_swipeable_get_snap_points (self->swipeable, &n); + get_bounds (self, points, n, self->initial_progress, &lower, &upper); + } else { + get_range (self, &lower, &upper); + } progress = self->progress + delta; progress = CLAMP (progress, lower, upper); @@ -359,7 +366,7 @@ get_end_progress (AdwSwipeTracker *self, gdouble velocity, gboolean is_touchpad) { - gdouble pos, decel, slope, lower, upper; + gdouble pos, decel, slope; g_autofree gdouble *points = NULL; gint n; @@ -387,9 +394,13 @@ get_end_progress (AdwSwipeTracker *self, pos = (pos * SIGN (velocity)) + self->progress; - get_bounds (self, points, n, self->initial_progress, &lower, &upper); + if (!self->allow_long_swipes) { + gdouble lower, upper; - pos = CLAMP (pos, lower, upper); + get_bounds (self, points, n, self->initial_progress, &lower, &upper); + + pos = CLAMP (pos, lower, upper); + } pos = points[find_point_for_projection (self, points, n, pos, velocity)]; @@ -954,6 +965,10 @@ adw_swipe_tracker_get_property (GObject *object, g_value_set_boolean (value, adw_swipe_tracker_get_allow_mouse_drag (self)); break; + case PROP_ALLOW_LONG_SWIPES: + g_value_set_boolean (value, adw_swipe_tracker_get_allow_long_swipes (self)); + break; + case PROP_ORIENTATION: g_value_set_enum (value, self->orientation); break; @@ -988,6 +1003,10 @@ adw_swipe_tracker_set_property (GObject *object, adw_swipe_tracker_set_allow_mouse_drag (self, g_value_get_boolean (value)); break; + case PROP_ALLOW_LONG_SWIPES: + adw_swipe_tracker_set_allow_long_swipes (self, g_value_get_boolean (value)); + break; + case PROP_ORIENTATION: set_orientation (self, g_value_get_enum (value)); break; @@ -1066,6 +1085,21 @@ adw_swipe_tracker_class_init (AdwSwipeTrackerClass *klass) FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + /** + * AdwSwipeTracker:allow-long-swipes: + * + * Whether to allow swiping for more than one snap point at a time. If the + * value is %FALSE, each swipe can only move to the adjacent snap points. + * + * Since: 1.0 + */ + props[PROP_ALLOW_LONG_SWIPES] = + g_param_spec_boolean ("allow-long-swipes", + _("Allow long swipes"), + _("Whether to allow swiping for more than one snap point at a time"), + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); @@ -1323,6 +1357,51 @@ adw_swipe_tracker_set_allow_mouse_drag (AdwSwipeTracker *self, g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ALLOW_MOUSE_DRAG]); } +/** + * adw_swipe_tracker_get_allow_long_swipes: + * @self: a #AdwSwipeTracker + * + * Whether to allow swiping for more than one snap point at a time. If the + * value is %FALSE, each swipe can only move to the adjacent snap points. + * + * Returns: %TRUE if long swipes are allowed, %FALSE otherwise + * + * Since: 1.0 + */ +gboolean +adw_swipe_tracker_get_allow_long_swipes (AdwSwipeTracker *self) +{ + g_return_val_if_fail (ADW_IS_SWIPE_TRACKER (self), FALSE); + + return self->allow_long_swipes; +} + +/** + * adw_swipe_tracker_set_allow_long_swipes: + * @self: a #AdwSwipeTracker + * @allow_long_swipes: whether to allow long swipes + * + * Sets whether to allow swiping for more than one snap point at a time. If the + * value is %FALSE, each swipe can only move to the adjacent snap points. + * + * Since: 1.0 + */ +void +adw_swipe_tracker_set_allow_long_swipes (AdwSwipeTracker *self, + gboolean allow_long_swipes) +{ + g_return_if_fail (ADW_IS_SWIPE_TRACKER (self)); + + allow_long_swipes = !!allow_long_swipes; + + if (self->allow_long_swipes == allow_long_swipes) + return; + + self->allow_long_swipes = allow_long_swipes; + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ALLOW_LONG_SWIPES]); +} + /** * adw_swipe_tracker_shift_position: * @self: a #AdwSwipeTracker diff --git a/src/adw-swipe-tracker.h b/src/adw-swipe-tracker.h index 9ff3645d7..0b0a9a2d7 100644 --- a/src/adw-swipe-tracker.h +++ b/src/adw-swipe-tracker.h @@ -46,6 +46,12 @@ ADW_AVAILABLE_IN_ALL void adw_swipe_tracker_set_allow_mouse_drag (AdwSwipeTracker *self, gboolean allow_mouse_drag); +ADW_AVAILABLE_IN_ALL +gboolean adw_swipe_tracker_get_allow_long_swipes (AdwSwipeTracker *self); +ADW_AVAILABLE_IN_ALL +void adw_swipe_tracker_set_allow_long_swipes (AdwSwipeTracker *self, + gboolean allow_long_swipes); + ADW_AVAILABLE_IN_ALL void adw_swipe_tracker_shift_position (AdwSwipeTracker *self, gdouble delta); -- GitLab From c6ded459e635154ec8b8e2c175b1937f9618fd01 Mon Sep 17 00:00:00 2001 From: Alexander Mikhaylenko Date: Sun, 12 Jul 2020 20:00:43 +0500 Subject: [PATCH 4/6] carousel: Add allow-long-swipes property Expose the AdwSwipeTracker property, as it's relevant here. --- src/adw-carousel.c | 69 +++++++++++++++++++++++++++++++++++++++++++ src/adw-carousel.h | 6 ++++ tests/test-carousel.c | 27 +++++++++++++++++ 3 files changed, 102 insertions(+) diff --git a/src/adw-carousel.c b/src/adw-carousel.c index 17df2706e..0d68c5650 100644 --- a/src/adw-carousel.c +++ b/src/adw-carousel.c @@ -68,6 +68,7 @@ enum { PROP_SPACING, PROP_ANIMATION_DURATION, PROP_ALLOW_MOUSE_DRAG, + PROP_ALLOW_LONG_SWIPES, PROP_REVEAL_DURATION, /* GtkOrientable */ @@ -389,6 +390,10 @@ adw_carousel_get_property (GObject *object, g_value_set_boolean (value, adw_carousel_get_allow_mouse_drag (self)); break; + case PROP_ALLOW_LONG_SWIPES: + g_value_set_boolean (value, adw_carousel_get_allow_long_swipes (self)); + break; + case PROP_REVEAL_DURATION: g_value_set_uint (value, adw_carousel_get_reveal_duration (self)); break; @@ -435,6 +440,10 @@ adw_carousel_set_property (GObject *object, adw_carousel_set_allow_mouse_drag (self, g_value_get_boolean (value)); break; + case PROP_ALLOW_LONG_SWIPES: + adw_carousel_set_allow_long_swipes (self, g_value_get_boolean (value)); + break; + case PROP_ORIENTATION: { GtkOrientation orientation = g_value_get_enum (value); @@ -567,6 +576,21 @@ adw_carousel_class_init (AdwCarouselClass *klass) TRUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + /** + * AdwCarousel:allow-long-swipes: + * + * Whether to allow swiping for more than one page at a time. If the value is + * %FALSE, each swipe can only move to the adjacent pages. + * + * Since: 1.0 + */ + props[PROP_ALLOW_LONG_SWIPES] = + g_param_spec_boolean ("allow-long-swipes", + _("Allow long swipes"), + _("Whether to allow swiping for more than one page at a time"), + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + /** * AdwCarousel:reveal-duration: * @@ -1053,6 +1077,51 @@ adw_carousel_set_allow_mouse_drag (AdwCarousel *self, g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ALLOW_MOUSE_DRAG]); } +/** + * adw_carousel_get_allow_long_swipes: + * @self: a #AdwCarousel + * + * Whether to allow swiping for more than one page at a time. If the value is + * %FALSE, each swipe can only move to the adjacent pages. + * + * Returns: %TRUE if long swipes are allowed, %FALSE otherwise + * + * Since: 1.0 + */ +gboolean +adw_carousel_get_allow_long_swipes (AdwCarousel *self) +{ + g_return_val_if_fail (ADW_IS_CAROUSEL (self), FALSE); + + return adw_swipe_tracker_get_allow_long_swipes (self->tracker); +} + +/** + * adw_carousel_set_allow_long_swipes: + * @self: a #AdwCarousel + * @allow_long_swipes: whether to allow long swipes + * + * Sets whether to allow swiping for more than one page at a time. If the value + * is %FALSE, each swipe can only move to the adjacent pages. + * + * Since: 1.0 + */ +void +adw_carousel_set_allow_long_swipes (AdwCarousel *self, + gboolean allow_long_swipes) +{ + g_return_if_fail (ADW_IS_CAROUSEL (self)); + + allow_long_swipes = !!allow_long_swipes; + + if (adw_swipe_tracker_get_allow_long_swipes (self->tracker) == allow_long_swipes) + return; + + adw_swipe_tracker_set_allow_long_swipes (self->tracker, allow_long_swipes); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ALLOW_LONG_SWIPES]); +} + /** * adw_carousel_get_reveal_duration: * @self: a #AdwCarousel diff --git a/src/adw-carousel.h b/src/adw-carousel.h index 0b69fafb5..0cda55c63 100644 --- a/src/adw-carousel.h +++ b/src/adw-carousel.h @@ -84,6 +84,12 @@ ADW_AVAILABLE_IN_ALL void adw_carousel_set_allow_mouse_drag (AdwCarousel *self, gboolean allow_mouse_drag); +ADW_AVAILABLE_IN_ALL +gboolean adw_carousel_get_allow_long_swipes (AdwCarousel *self); +ADW_AVAILABLE_IN_ALL +void adw_carousel_set_allow_long_swipes (AdwCarousel *self, + gboolean allow_long_swipes); + ADW_AVAILABLE_IN_ALL guint adw_carousel_get_reveal_duration (AdwCarousel *self); ADW_AVAILABLE_IN_ALL diff --git a/tests/test-carousel.c b/tests/test-carousel.c index 13e836fe3..1038e9e10 100644 --- a/tests/test-carousel.c +++ b/tests/test-carousel.c @@ -166,6 +166,32 @@ test_adw_carousel_allow_mouse_drag (void) g_assert_cmpint (notified, ==, 2); } +static void +test_adw_carousel_allow_long_swipes (void) +{ + AdwCarousel *carousel = ADW_CAROUSEL (adw_carousel_new ()); + gboolean allow_long_swipes; + + notified = 0; + g_signal_connect (carousel, "notify::allow-long-swipes", G_CALLBACK (notify_cb), NULL); + + /* Accessors */ + g_assert_false (adw_carousel_get_allow_long_swipes (carousel)); + adw_carousel_set_allow_long_swipes (carousel, TRUE); + g_assert_true (adw_carousel_get_allow_long_swipes (carousel)); + g_assert_cmpint (notified, ==, 1); + + /* Property */ + g_object_set (carousel, "allow-long-swipes", FALSE, NULL); + g_object_get (carousel, "allow-long-swipes", &allow_long_swipes, NULL); + g_assert_false (allow_long_swipes); + g_assert_cmpint (notified, ==, 2); + + /* Setting the same value should not notify */ + adw_carousel_set_allow_long_swipes (carousel, FALSE); + g_assert_cmpint (notified, ==, 2); +} + static void test_adw_carousel_reveal_duration (void) { @@ -204,6 +230,7 @@ main (gint argc, g_test_add_func("/Adwaita/Carousel/spacing", test_adw_carousel_spacing); g_test_add_func("/Adwaita/Carousel/animation_duration", test_adw_carousel_animation_duration); g_test_add_func("/Adwaita/Carousel/allow_mouse_drag", test_adw_carousel_allow_mouse_drag); + g_test_add_func("/Adwaita/Carousel/allow_long_swipes", test_adw_carousel_allow_long_swipes); g_test_add_func("/Adwaita/Carousel/reveal_duration", test_adw_carousel_reveal_duration); return g_test_run(); } -- GitLab From 60e66f34c00db15585802a77d280fdf93b6f0fb8 Mon Sep 17 00:00:00 2001 From: Alexander Mikhaylenko Date: Sun, 12 Jul 2020 20:23:46 +0500 Subject: [PATCH 5/6] examples: Add a long swipe switch to the carousel page --- examples/adw-demo-window.ui | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/adw-demo-window.ui b/examples/adw-demo-window.ui index c4e2446b1..40a5b792e 100644 --- a/examples/adw-demo-window.ui +++ b/examples/adw-demo-window.ui @@ -598,6 +598,7 @@ True True + widget-carousel-symbolic @@ -650,6 +651,17 @@ + + + Long swipes + carousel_long_swipes + + + center + + + + -- GitLab From 07403f12dfb05ca7d4362d6da55ffa5b2932acea Mon Sep 17 00:00:00 2001 From: Alexander Mikhaylenko Date: Mon, 8 Feb 2021 17:35:08 +0500 Subject: [PATCH 6/6] swipe-tracker: A small cleanup Actually use the `pos` parameter in get_bounds(). --- src/adw-swipe-tracker.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/adw-swipe-tracker.c b/src/adw-swipe-tracker.c index 5258bf68b..d491f27aa 100644 --- a/src/adw-swipe-tracker.c +++ b/src/adw-swipe-tracker.c @@ -319,13 +319,13 @@ get_bounds (AdwSwipeTracker *self, gdouble *upper) { gint prev, next; - gint closest = find_closest_point (points, n, self->initial_progress); + gint closest = find_closest_point (points, n, pos); - if (ABS (points[closest] - self->initial_progress) < EPSILON) { + if (ABS (points[closest] - pos) < EPSILON) { prev = next = closest; } else { - prev = find_previous_point (points, n, self->initial_progress); - next = find_next_point (points, n, self->initial_progress); + prev = find_previous_point (points, n, pos); + next = find_next_point (points, n, pos); } *lower = points[MAX (prev - 1, 0)]; -- GitLab