diff --git a/examples/adw-demo-window.ui b/examples/adw-demo-window.ui index c4e2446b1d28ea00664a05eca2b57dbfb2940240..40a5b792e9987c0e6b1360e698d68225ae21ffa3 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 + + + + diff --git a/src/adw-carousel.c b/src/adw-carousel.c index 17df2706e83d967ee2f9e572603762f1bb103b09..0d68c56507ca7ac5e2da03beedb9c2853afe74cc 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 0b69fafb5f26c9f648c86bdeafa43c70a80607a1..0cda55c63dfc74584c92e412523fe93b88c69665 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/src/adw-swipe-tracker.c b/src/adw-swipe-tracker.c index 64a2e34ab0a8d33eca1649f36e50e1c8ffc93a78..d491f27aa9d34f9a9d677b5b2f134b84a9ea305d 100644 --- a/src/adw-swipe-tracker.c +++ b/src/adw-swipe-tracker.c @@ -14,13 +14,22 @@ #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 -#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 @@ -47,6 +56,11 @@ typedef enum { ADW_SWIPE_TRACKER_STATE_REJECTED, } AdwSwipeTrackerState; +typedef struct { + gdouble delta; + guint32 time; +} EventHistoryRecord; + struct _AdwSwipeTracker { GObject parent_instance; @@ -55,18 +69,18 @@ struct _AdwSwipeTracker gboolean enabled; gboolean reversed; gboolean allow_mouse_drag; + gboolean allow_long_swipes; GtkOrientation orientation; 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; @@ -90,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]; @@ -117,13 +132,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,119 +180,261 @@ 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, + gdouble delta, + guint32 time) +{ + 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; } +static gint +find_closest_point (gdouble *points, + gint n, + gdouble pos) +{ + guint i, min = 0; + + for (i = 1; i < n; i++) + if (ABS (points[i] - pos) < ABS (points[min] - pos)) + min = i; + + return min; +} + +static gint +find_next_point (gdouble *points, + gint n, + gdouble pos) +{ + guint i; + + for (i = 0; i < n; i++) + if (points[i] >= pos) + return i; + + return -1; +} + +static gint +find_previous_point (gdouble *points, + gint n, + gdouble pos) +{ + gint i; + + for (i = n - 1; i >= 0; i--) + if (points[i] <= pos) + return i; + + 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_bounds (AdwSwipeTracker *self, + gdouble *points, + gint n, + gdouble pos, + gdouble *lower, + gdouble *upper) +{ + gint prev, next; + gint closest = find_closest_point (points, n, pos); + + if (ABS (points[closest] - pos) < EPSILON) { + prev = next = closest; + } else { + prev = find_previous_point (points, n, pos); + next = find_next_point (points, n, pos); + } + + *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; - gdouble first_point, last_point; if (self->state != ADW_SWIPE_TRACKER_STATE_SCROLLING) return; - if (time != self->prev_time) - self->velocity = delta / (time - self->prev_time); + if (!self->allow_long_swipes) { + g_autofree gdouble *points = NULL; + gint n; - get_range (self, &first_point, &last_point); + 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, first_point, last_point); - - /* 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); + progress = CLAMP (progress, lower, upper); self->progress = progress; adw_swipe_tracker_emit_update_swipe (self, progress); - - self->prev_time = time; } -static void -get_closest_snap_points (AdwSwipeTracker *self, - gdouble *upper, - gdouble *lower) +static gdouble +get_end_progress (AdwSwipeTracker *self, + gdouble velocity, + gboolean is_touchpad) { - gint i, n; - gdouble *points; + gdouble pos, decel, slope; + g_autofree gdouble *points = NULL; + gint n; - *upper = 0; - *lower = 0; + if (self->cancelled) + return adw_swipeable_get_cancel_progress (self->swipeable); points = adw_swipeable_get_snap_points (self->swipeable, &n); - for (i = 0; i < n; i++) { - if (points[i] >= self->progress) { - *upper = points[i]; - break; - } - } + if (ABS (velocity) < (is_touchpad ? VELOCITY_THRESHOLD_TOUCHPAD : VELOCITY_THRESHOLD_TOUCH)) + return points[find_closest_point (points, n, self->progress)]; - for (i = n - 1; i >= 0; i--) { - if (points[i] <= self->progress) { - *lower = points[i]; - break; - } + 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; } - g_free (points); -} + pos = (pos * SIGN (velocity)) + self->progress; -static gdouble -get_end_progress (AdwSwipeTracker *self, - gdouble distance) -{ - gdouble upper, lower, middle; + if (!self->allow_long_swipes) { + gdouble lower, upper; - if (self->cancelled) - return adw_swipeable_get_cancel_progress (self->swipeable); + get_bounds (self, points, n, self->initial_progress, &lower, &upper); - get_closest_snap_points (self, &upper, &lower); - middle = (upper + lower) / 2; + pos = CLAMP (pos, lower, upper); + } - if (self->progress > middle) - return (self->velocity * distance > -VELOCITY_THRESHOLD || - self->initial_progress > upper) ? upper : lower; + pos = points[find_point_for_projection (self, points, n, pos, velocity)]; - return (self->velocity * distance < VELOCITY_THRESHOLD || - self->initial_progress < lower) ? lower : upper; + return pos; } static void gesture_end (AdwSwipeTracker *self, - gdouble distance) + gdouble distance, + guint32 time, + gboolean is_touchpad) { gdouble end_progress, velocity; - gint64 duration; + gint64 duration, max_duration; if (self->state == ADW_SWIPE_TRACKER_STATE_NONE) return; - end_progress = get_end_progress (self, distance); + trim_history (self, time); - velocity = ANIMATION_BASE_VELOCITY; - if ((end_progress - self->progress) * self->velocity > 0) - velocity = self->velocity; + velocity = calculate_velocity (self); + + 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); @@ -290,7 +446,9 @@ gesture_end (AdwSwipeTracker *self, static void gesture_cancel (AdwSwipeTracker *self, - gdouble distance) + gdouble distance, + guint32 time, + gboolean is_touchpad) { if (self->state != ADW_SWIPE_TRACKER_STATE_PENDING && self->state != ADW_SWIPE_TRACKER_STATE_SCROLLING) { @@ -300,7 +458,7 @@ gesture_cancel (AdwSwipeTracker *self, } self->cancelled = TRUE; - gesture_end (self, distance); + gesture_end (self, distance, time, is_touchpad); } static gboolean @@ -423,21 +581,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) { @@ -445,6 +603,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); @@ -453,8 +615,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; @@ -468,7 +628,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 { @@ -477,10 +637,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 @@ -490,6 +648,7 @@ drag_end_cb (AdwSwipeTracker *self, GtkGestureDrag *gesture) { gdouble distance; + guint32 time; distance = adw_swipeable_get_distance (self->swipeable); @@ -500,13 +659,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, FALSE); gtk_gesture_set_state (self->touch_gesture, GTK_EVENT_SEQUENCE_DENIED); return; } - gesture_end (self, distance); + gesture_end (self, distance, time, FALSE); } static void @@ -514,11 +675,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, FALSE); gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED); } @@ -576,16 +740,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, time); + gesture_begin (self); else - gesture_cancel (self, distance); + 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); + 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; } @@ -797,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; @@ -831,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; @@ -909,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"); @@ -982,7 +1173,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; } @@ -1164,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 9ff3645d77bc892e311bb59b74aa872b615c33c3..0b0a9a2d7219b0ad34f1db841c6b8deecd8ca7cf 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); diff --git a/tests/test-carousel.c b/tests/test-carousel.c index 13e836fe3ead02a19acaba2a32bd5eeb683ed23f..1038e9e10a78ff14f3293f37e8f56dae0ce30a51 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(); }