scrolledwindow: port to GtkGesture

The kinetic scrolling feature is now implemented using a
GtkGestureDrag and a GtkGestureSwipe, plus a GtkGestureLongPress
in order to denying the sequence, so it is possibly handled
underneath.
This commit is contained in:
Carlos Garnacho 2014-02-26 15:48:21 +01:00
parent 76213ab209
commit 83ef067ae1

View File

@ -130,7 +130,6 @@
#define MAX_OVERSHOOT_DISTANCE 50
#define FRICTION_DECELERATION 0.003
#define OVERSHOOT_INVERSE_ACCELERATION 0.003
#define RELEASE_EVENT_TIMEOUT 1000
struct _GtkScrolledWindowPrivate
{
@ -150,24 +149,19 @@ struct _GtkScrolledWindowPrivate
gint min_content_height;
/* Kinetic scrolling */
GdkEvent *button_press_event;
GtkGesture *drag_gesture;
GtkGesture *swipe_gesture;
GtkGesture *long_press_gesture;
gdouble drag_start_x;
gdouble drag_start_y;
GdkWindow *overshoot_window;
GdkDevice *drag_device;
guint kinetic_scrolling : 1;
guint capture_button_press : 1;
guint in_drag : 1;
guint last_button_event_valid : 1;
guint release_timeout_id;
guint deceleration_id;
gdouble last_button_event_x_root;
gdouble last_button_event_y_root;
gdouble last_motion_event_x_root;
gdouble last_motion_event_y_root;
guint32 last_motion_event_time;
gdouble x_velocity;
gdouble y_velocity;
@ -224,8 +218,6 @@ static void gtk_scrolled_window_size_allocate (GtkWidget *widge
GtkAllocation *allocation);
static gboolean gtk_scrolled_window_scroll_event (GtkWidget *widget,
GdkEventScroll *event);
static gboolean gtk_scrolled_window_captured_event (GtkWidget *widget,
GdkEvent *event);
static gboolean gtk_scrolled_window_focus (GtkWidget *widget,
GtkDirectionType direction);
static void gtk_scrolled_window_add (GtkContainer *container,
@ -280,6 +272,13 @@ static gboolean _gtk_scrolled_window_set_adjustment_value (GtkScrolledWindo
static void gtk_scrolled_window_cancel_deceleration (GtkScrolledWindow *scrolled_window);
static gboolean _gtk_scrolled_window_get_overshoot (GtkScrolledWindow *scrolled_window,
gint *overshoot_x,
gint *overshoot_y);
static void _gtk_scrolled_window_allocate_overshoot_window (GtkScrolledWindow *scrolled_window);
static void gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window);
static guint signals[LAST_SIGNAL] = {0};
G_DEFINE_TYPE_WITH_PRIVATE (GtkScrolledWindow, gtk_scrolled_window, GTK_TYPE_BIN)
@ -562,16 +561,132 @@ gtk_scrolled_window_class_init (GtkScrolledWindowClass *class)
gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SCROLLED_WINDOW_ACCESSIBLE);
}
static void
scrolled_window_drag_begin_cb (GtkScrolledWindow *scrolled_window,
gdouble start_x,
gdouble start_y,
GtkGesture *gesture)
{
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
GdkEventSequence *sequence;
priv->drag_start_x = priv->unclamped_hadj_value;
priv->drag_start_y = priv->unclamped_vadj_value;
gtk_scrolled_window_cancel_deceleration (scrolled_window);
if (!priv->hscrollbar_visible && !priv->vscrollbar_visible)
{
sequence = gtk_gesture_get_last_updated_sequence (gesture);
gtk_widget_set_sequence_state (GTK_WIDGET (scrolled_window),
sequence, GTK_EVENT_SEQUENCE_DENIED);
}
}
static void
scrolled_window_drag_update_cb (GtkScrolledWindow *scrolled_window,
gdouble offset_x,
gdouble offset_y,
GtkGesture *gesture)
{
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
gint old_overshoot_x, old_overshoot_y;
gint new_overshoot_x, new_overshoot_y;
GtkAdjustment *hadjustment;
GtkAdjustment *vadjustment;
GdkEventSequence *sequence;
gdouble dx, dy;
sequence = gtk_gesture_get_last_updated_sequence (gesture);
gtk_widget_set_sequence_state (GTK_WIDGET (scrolled_window),
sequence, GTK_EVENT_SEQUENCE_CLAIMED);
_gtk_scrolled_window_get_overshoot (scrolled_window,
&old_overshoot_x, &old_overshoot_y);
hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
if (hadjustment && priv->hscrollbar_visible)
{
dx = priv->drag_start_x - offset_x + old_overshoot_x;
_gtk_scrolled_window_set_adjustment_value (scrolled_window, hadjustment,
dx, TRUE, FALSE);
}
vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
if (vadjustment && priv->vscrollbar_visible)
{
dy = priv->drag_start_y - offset_y + old_overshoot_y;
_gtk_scrolled_window_set_adjustment_value (scrolled_window, vadjustment,
dy, TRUE, FALSE);
}
_gtk_scrolled_window_get_overshoot (scrolled_window,
&new_overshoot_x, &new_overshoot_y);
if (old_overshoot_x != new_overshoot_x ||
old_overshoot_y != new_overshoot_y)
{
if (new_overshoot_x >= 0 || new_overshoot_y >= 0)
{
/* We need to reallocate the widget to have it at
* negative offset, so there's a "gravity" on the
* bottom/right corner
*/
gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
}
else if (new_overshoot_x < 0 || new_overshoot_y < 0)
_gtk_scrolled_window_allocate_overshoot_window (scrolled_window);
}
}
static void
scrolled_window_swipe_cb (GtkScrolledWindow *scrolled_window,
gdouble x_velocity,
gdouble y_velocity)
{
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
gboolean overshoot;
overshoot = _gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL);
priv->x_velocity = -x_velocity / 1000;
priv->y_velocity = -y_velocity / 1000;
/* Zero out vector components without a visible scrollbar */
if (!priv->hscrollbar_visible)
priv->x_velocity = 0;
if (!priv->vscrollbar_visible)
priv->y_velocity = 0;
if (priv->x_velocity != 0 || priv->y_velocity != 0 || overshoot)
{
gtk_scrolled_window_start_deceleration (scrolled_window);
priv->x_velocity = priv->y_velocity = 0;
}
}
static void
scrolled_window_long_press_cb (GtkScrolledWindow *scrolled_window,
gdouble x,
gdouble y,
GtkGesture *gesture)
{
GdkEventSequence *sequence;
sequence = gtk_gesture_get_last_updated_sequence (gesture);
gtk_widget_set_sequence_state (GTK_WIDGET (scrolled_window),
sequence, GTK_EVENT_SEQUENCE_DENIED);
}
static void
gtk_scrolled_window_init (GtkScrolledWindow *scrolled_window)
{
GtkWidget *widget = GTK_WIDGET (scrolled_window);
GtkScrolledWindowPrivate *priv;
scrolled_window->priv = priv =
gtk_scrolled_window_get_instance_private (scrolled_window);
gtk_widget_set_has_window (GTK_WIDGET (scrolled_window), FALSE);
gtk_widget_set_can_focus (GTK_WIDGET (scrolled_window), TRUE);
gtk_widget_set_has_window (widget, FALSE);
gtk_widget_set_can_focus (widget, TRUE);
/* Instantiated by gtk_scrolled_window_set_[hv]adjustment
* which are both construct properties
@ -587,6 +702,23 @@ gtk_scrolled_window_init (GtkScrolledWindow *scrolled_window)
priv->min_content_width = -1;
priv->min_content_height = -1;
priv->drag_gesture = gtk_gesture_drag_new (widget);
g_signal_connect_swapped (priv->drag_gesture, "drag-begin",
G_CALLBACK (scrolled_window_drag_begin_cb),
scrolled_window);
g_signal_connect_swapped (priv->drag_gesture, "drag-update",
G_CALLBACK (scrolled_window_drag_update_cb),
scrolled_window);
priv->swipe_gesture = gtk_gesture_swipe_new (widget);
g_signal_connect_swapped (priv->swipe_gesture, "swipe",
G_CALLBACK (scrolled_window_swipe_cb),
scrolled_window);
priv->long_press_gesture = gtk_gesture_long_press_new (widget);
g_signal_connect_swapped (priv->long_press_gesture, "pressed",
G_CALLBACK (scrolled_window_long_press_cb),
scrolled_window);
gtk_scrolled_window_set_kinetic_scrolling (scrolled_window, TRUE);
gtk_scrolled_window_set_capture_button_press (scrolled_window, TRUE);
}
@ -1060,24 +1192,29 @@ gtk_scrolled_window_set_kinetic_scrolling (GtkScrolledWindow *scrolled_window,
return;
priv->kinetic_scrolling = kinetic_scrolling;
if (priv->kinetic_scrolling)
{
_gtk_widget_set_captured_event_handler (GTK_WIDGET (scrolled_window),
gtk_scrolled_window_captured_event);
gtk_widget_add_controller (GTK_WIDGET (scrolled_window),
GTK_EVENT_CONTROLLER (priv->drag_gesture),
GTK_PHASE_CAPTURE);
gtk_widget_add_controller (GTK_WIDGET (scrolled_window),
GTK_EVENT_CONTROLLER (priv->swipe_gesture),
GTK_PHASE_CAPTURE);
gtk_widget_add_controller (GTK_WIDGET (scrolled_window),
GTK_EVENT_CONTROLLER (priv->long_press_gesture),
GTK_PHASE_CAPTURE);
}
else
{
_gtk_widget_set_captured_event_handler (GTK_WIDGET (scrolled_window), NULL);
if (priv->release_timeout_id)
{
g_source_remove (priv->release_timeout_id);
priv->release_timeout_id = 0;
}
if (priv->deceleration_id)
{
gtk_widget_remove_tick_callback (GTK_WIDGET (scrolled_window), priv->deceleration_id);
priv->deceleration_id = 0;
}
gtk_widget_remove_controller (GTK_WIDGET (scrolled_window),
GTK_EVENT_CONTROLLER (priv->drag_gesture));
gtk_widget_remove_controller (GTK_WIDGET (scrolled_window),
GTK_EVENT_CONTROLLER (priv->swipe_gesture));
gtk_widget_remove_controller (GTK_WIDGET (scrolled_window),
GTK_EVENT_CONTROLLER (priv->long_press_gesture));
gtk_scrolled_window_cancel_deceleration (scrolled_window);
}
g_object_notify (G_OBJECT (scrolled_window), "kinetic-scrolling");
}
@ -1175,21 +1312,22 @@ gtk_scrolled_window_destroy (GtkWidget *widget)
priv->vscrollbar = NULL;
}
if (priv->release_timeout_id)
{
g_source_remove (priv->release_timeout_id);
priv->release_timeout_id = 0;
}
if (priv->deceleration_id)
{
gtk_widget_remove_tick_callback (widget, priv->deceleration_id);
priv->deceleration_id = 0;
}
if (priv->button_press_event)
if (priv->drag_gesture)
{
gdk_event_free (priv->button_press_event);
priv->button_press_event = NULL;
g_object_unref (priv->drag_gesture);
priv->drag_gesture = NULL;
}
if (priv->swipe_gesture)
{
g_object_unref (priv->swipe_gesture);
priv->swipe_gesture = NULL;
}
GTK_WIDGET_CLASS (gtk_scrolled_window_parent_class)->destroy (widget);
@ -2414,388 +2552,6 @@ gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window)
(GDestroyNotify) g_free);
}
static gboolean
gtk_scrolled_window_release_captured_event (GtkScrolledWindow *scrolled_window)
{
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
/* Cancel the scrolling and send the button press
* event to the child widget
*/
if (!priv->button_press_event)
return FALSE;
if (priv->drag_device)
{
gtk_device_grab_remove (GTK_WIDGET (scrolled_window), priv->drag_device);
priv->drag_device = NULL;
}
if (priv->capture_button_press)
{
GtkWidget *event_widget;
event_widget = gtk_get_event_widget (priv->button_press_event);
if (!_gtk_propagate_captured_event (event_widget,
priv->button_press_event,
gtk_bin_get_child (GTK_BIN (scrolled_window))))
gtk_propagate_event (event_widget, priv->button_press_event);
gdk_event_free (priv->button_press_event);
priv->button_press_event = NULL;
}
if (_gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL))
gtk_scrolled_window_start_deceleration (scrolled_window);
return FALSE;
}
static gboolean
gtk_scrolled_window_calculate_velocity (GtkScrolledWindow *scrolled_window,
GdkEvent *event)
{
GtkScrolledWindowPrivate *priv;
gdouble x_root, y_root;
guint32 _time;
#define STILL_THRESHOLD 40
if (!gdk_event_get_root_coords (event, &x_root, &y_root))
return FALSE;
priv = scrolled_window->priv;
_time = gdk_event_get_time (event);
if (priv->last_motion_event_x_root != x_root ||
priv->last_motion_event_y_root != y_root ||
ABS (_time - priv->last_motion_event_time) > STILL_THRESHOLD)
{
priv->x_velocity = (priv->last_motion_event_x_root - x_root) /
(gdouble) (_time - priv->last_motion_event_time);
priv->y_velocity = (priv->last_motion_event_y_root - y_root) /
(gdouble) (_time - priv->last_motion_event_time);
}
priv->last_motion_event_x_root = x_root;
priv->last_motion_event_y_root = y_root;
priv->last_motion_event_time = _time;
#undef STILL_THRESHOLD
return TRUE;
}
static gboolean
gtk_scrolled_window_captured_button_release (GtkWidget *widget,
GdkEvent *event)
{
GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
GtkWidget *child;
gboolean overshoot;
guint button;
gdouble x_root, y_root;
if (gdk_event_get_button (event, &button) && button != 1)
return FALSE;
child = gtk_bin_get_child (GTK_BIN (widget));
if (!child)
return FALSE;
gtk_device_grab_remove (widget, priv->drag_device);
priv->drag_device = NULL;
if (priv->release_timeout_id)
{
g_source_remove (priv->release_timeout_id);
priv->release_timeout_id = 0;
}
overshoot = _gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL);
if (priv->in_drag)
gdk_device_ungrab (gdk_event_get_device (event), gdk_event_get_time (event));
else
{
/* There hasn't been scrolling at all, so just let the
* child widget handle the button press normally
*/
gtk_scrolled_window_release_captured_event (scrolled_window);
if (!overshoot)
return FALSE;
}
priv->in_drag = FALSE;
if (priv->button_press_event)
{
gdk_event_free (priv->button_press_event);
priv->button_press_event = NULL;
}
gtk_scrolled_window_calculate_velocity (scrolled_window, event);
/* Zero out vector components without a visible scrollbar */
if (!priv->hscrollbar_visible)
priv->x_velocity = 0;
if (!priv->vscrollbar_visible)
priv->y_velocity = 0;
if (priv->x_velocity != 0 || priv->y_velocity != 0 || overshoot)
{
gtk_scrolled_window_start_deceleration (scrolled_window);
priv->x_velocity = priv->y_velocity = 0;
priv->last_button_event_valid = FALSE;
}
else
{
gdk_event_get_root_coords (event, &x_root, &y_root);
priv->last_button_event_x_root = x_root;
priv->last_button_event_y_root = y_root;
priv->last_button_event_valid = TRUE;
}
if (priv->capture_button_press)
return TRUE;
else
return FALSE;
}
static gboolean
gtk_scrolled_window_captured_motion_notify (GtkWidget *widget,
GdkEvent *event)
{
GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
gint old_overshoot_x, old_overshoot_y;
gint new_overshoot_x, new_overshoot_y;
GtkWidget *child;
GtkAdjustment *hadjustment;
GtkAdjustment *vadjustment;
gdouble dx, dy;
GdkModifierType state;
gdouble x_root, y_root;
gdk_event_get_state (event, &state);
if (!(state & GDK_BUTTON1_MASK))
return FALSE;
child = gtk_bin_get_child (GTK_BIN (widget));
if (!child)
return FALSE;
/* Check if we've passed the drag threshold */
gdk_event_get_root_coords (event, &x_root, &y_root);
if (!priv->in_drag)
{
if (gtk_drag_check_threshold (widget,
priv->last_button_event_x_root,
priv->last_button_event_y_root,
x_root, y_root))
{
if (priv->release_timeout_id)
{
g_source_remove (priv->release_timeout_id);
priv->release_timeout_id = 0;
}
priv->last_button_event_valid = FALSE;
priv->in_drag = TRUE;
}
else
return TRUE;
}
gdk_device_grab (priv->drag_device,
gtk_widget_get_window (widget),
GDK_OWNERSHIP_WINDOW,
TRUE,
GDK_BUTTON_RELEASE_MASK | GDK_BUTTON1_MOTION_MASK,
NULL,
gdk_event_get_time (event));
priv->last_button_event_valid = FALSE;
if (priv->button_press_event)
{
gdk_event_free (priv->button_press_event);
priv->button_press_event = NULL;
}
_gtk_scrolled_window_get_overshoot (scrolled_window,
&old_overshoot_x, &old_overshoot_y);
hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
if (hadjustment && priv->hscrollbar_visible)
{
dx = (priv->last_motion_event_x_root - x_root) + priv->unclamped_hadj_value;
_gtk_scrolled_window_set_adjustment_value (scrolled_window, hadjustment,
dx, TRUE, FALSE);
}
vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
if (vadjustment && priv->vscrollbar_visible)
{
dy = (priv->last_motion_event_y_root - y_root) + priv->unclamped_vadj_value;
_gtk_scrolled_window_set_adjustment_value (scrolled_window, vadjustment,
dy, TRUE, FALSE);
}
_gtk_scrolled_window_get_overshoot (scrolled_window,
&new_overshoot_x, &new_overshoot_y);
if (old_overshoot_x != new_overshoot_x ||
old_overshoot_y != new_overshoot_y)
{
if (new_overshoot_x >= 0 || new_overshoot_y >= 0)
{
/* We need to reallocate the widget to have it at
* negative offset, so there's a "gravity" on the
* bottom/right corner
*/
gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
}
else if (new_overshoot_x < 0 || new_overshoot_y < 0)
_gtk_scrolled_window_allocate_overshoot_window (scrolled_window);
}
gtk_scrolled_window_calculate_velocity (scrolled_window, event);
return TRUE;
}
static gboolean
gtk_scrolled_window_captured_button_press (GtkWidget *widget,
GdkEvent *event)
{
GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
GtkWidget *child;
GtkWidget *event_widget;
GdkDevice *source_device;
GdkInputSource source;
gdouble x_root, y_root;
guint button;
/* If scrollbars are not visible, we don't do kinetic scrolling */
if (!priv->vscrollbar_visible && !priv->hscrollbar_visible)
return FALSE;
source_device = gdk_event_get_source_device (event);
source = gdk_device_get_source (source_device);
if (source != GDK_SOURCE_TOUCHSCREEN)
return FALSE;
event_widget = gtk_get_event_widget (event);
/* If there's another scrolled window between the widget
* receiving the event and this capturing scrolled window,
* let it handle the events.
*/
if (widget != gtk_widget_get_ancestor (event_widget, GTK_TYPE_SCROLLED_WINDOW))
return FALSE;
/* Check whether the button press is close to the previous one,
* take that as a shortcut to get the child widget handle events
*/
gdk_event_get_root_coords (event, &x_root, &y_root);
if (priv->last_button_event_valid &&
ABS (x_root - priv->last_button_event_x_root) < TOUCH_BYPASS_CAPTURED_THRESHOLD &&
ABS (y_root - priv->last_button_event_y_root) < TOUCH_BYPASS_CAPTURED_THRESHOLD)
{
priv->last_button_event_valid = FALSE;
return FALSE;
}
priv->last_button_event_x_root = priv->last_motion_event_x_root = x_root;
priv->last_button_event_y_root = priv->last_motion_event_y_root = y_root;
priv->last_motion_event_time = gdk_event_get_time (event);
priv->last_button_event_valid = TRUE;
if (gdk_event_get_button (event, &button) && button != 1)
return FALSE;
child = gtk_bin_get_child (GTK_BIN (widget));
if (!child)
return FALSE;
if (priv->hscrollbar == event_widget || priv->vscrollbar == event_widget)
return FALSE;
priv->drag_device = gdk_event_get_device (event);
gtk_device_grab_add (widget, priv->drag_device, TRUE);
gtk_scrolled_window_cancel_deceleration (scrolled_window);
/* Only set the timeout if we're going to store an event */
if (priv->capture_button_press)
{
priv->release_timeout_id =
gdk_threads_add_timeout (RELEASE_EVENT_TIMEOUT,
(GSourceFunc) gtk_scrolled_window_release_captured_event,
scrolled_window);
g_source_set_name_by_id (priv->release_timeout_id, "[gtk+] gtk_scrolled_window_release_captured_event");
}
priv->in_drag = FALSE;
if (priv->capture_button_press)
{
/* Store the button press event in
* case we need to propagate it later
*/
priv->button_press_event = gdk_event_copy (event);
return TRUE;
}
else
return FALSE;
}
static gboolean
gtk_scrolled_window_captured_event (GtkWidget *widget,
GdkEvent *event)
{
gboolean retval = FALSE;
GtkScrolledWindowPrivate *priv = GTK_SCROLLED_WINDOW (widget)->priv;
if (gdk_window_get_window_type (event->any.window) == GDK_WINDOW_TEMP)
return FALSE;
switch (event->type)
{
case GDK_TOUCH_BEGIN:
case GDK_BUTTON_PRESS:
retval = gtk_scrolled_window_captured_button_press (widget, event);
break;
case GDK_TOUCH_END:
case GDK_BUTTON_RELEASE:
if (priv->drag_device)
retval = gtk_scrolled_window_captured_button_release (widget, event);
else
priv->last_button_event_valid = FALSE;
break;
case GDK_TOUCH_UPDATE:
case GDK_MOTION_NOTIFY:
if (priv->drag_device)
retval = gtk_scrolled_window_captured_motion_notify (widget, event);
break;
case GDK_LEAVE_NOTIFY:
case GDK_ENTER_NOTIFY:
if (priv->in_drag &&
event->crossing.mode != GDK_CROSSING_GRAB)
retval = TRUE;
break;
default:
break;
}
return retval;
}
static gboolean
gtk_scrolled_window_focus (GtkWidget *widget,
GtkDirectionType direction)
@ -3335,20 +3091,11 @@ gtk_scrolled_window_grab_notify (GtkWidget *widget,
gdk_device_ungrab (priv->drag_device,
gtk_get_current_event_time ());
priv->drag_device = NULL;
priv->in_drag = FALSE;
if (priv->release_timeout_id)
{
g_source_remove (priv->release_timeout_id);
priv->release_timeout_id = 0;
}
if (_gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL))
gtk_scrolled_window_start_deceleration (scrolled_window);
else
gtk_scrolled_window_cancel_deceleration (scrolled_window);
priv->last_button_event_valid = FALSE;
}
}