switch: Use GtkGesture to handle input events

A pan gesture is used to handle switch dragging, which is only triggered
by horizontal panning movements. A multipress gesture handles the cases
where clicking without dragging happens, just toggling the switch.
This commit is contained in:
Carlos Garnacho 2014-05-19 16:08:56 +02:00
parent a47d0406a7
commit 8f20780fb9

View File

@ -62,6 +62,9 @@ struct _GtkSwitchPrivate
GtkAction *action;
GtkActionHelper *action_helper;
GtkGesture *pan_gesture;
GtkGesture *multipress_gesture;
gint handle_x;
gint offset;
gint drag_start;
@ -69,8 +72,6 @@ struct _GtkSwitchPrivate
guint state : 1;
guint is_active : 1;
guint is_dragging : 1;
guint in_press : 1;
guint in_switch : 1;
guint use_action_appearance : 1;
};
@ -110,171 +111,131 @@ G_DEFINE_TYPE_WITH_CODE (GtkSwitch, gtk_switch, GTK_TYPE_WIDGET,
gtk_switch_activatable_interface_init));
G_GNUC_END_IGNORE_DEPRECATIONS;
static gboolean
gtk_switch_button_press (GtkWidget *widget,
GdkEventButton *event)
static void
gtk_switch_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
gint n_press,
gdouble x,
gdouble y,
GtkSwitch *sw)
{
GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
GtkSwitchPrivate *priv = sw->priv;
GtkAllocation allocation;
/* Don't handle extra mouse buttons events, let them bubble up */
if (event->button > 5)
return GDK_EVENT_PROPAGATE;
gtk_widget_get_allocation (widget, &allocation);
gtk_widget_get_allocation (GTK_WIDGET (sw), &allocation);
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
if (priv->is_active)
{
/* if the event occurred in the "off" area, then this
* is a direct toggle
*/
if (event->x <= allocation.width / 2)
{
priv->in_press = TRUE;
return GDK_EVENT_STOP;
}
priv->offset = event->x - allocation.width / 2;
}
priv->offset = allocation.width / 2;
else
{
/* if the event occurred in the "on" area, then this
* is a direct toggle
*/
if (event->x >= allocation.width / 2)
{
priv->in_press = TRUE;
return GDK_EVENT_STOP;
}
priv->offset = 0;
priv->offset = event->x;
}
priv->drag_start = event->x;
g_object_get (gtk_widget_get_settings (widget),
"gtk-dnd-drag-threshold", &priv->drag_threshold,
NULL);
return GDK_EVENT_STOP;
/* If the press didn't happen in the draggable handle,
* cancel the pan gesture right away
*/
if ((priv->is_active && x <= allocation.width / 2) ||
(!priv->is_active && x > allocation.width / 2))
gtk_gesture_set_state (priv->pan_gesture, GTK_EVENT_SEQUENCE_DENIED);
}
static gboolean
gtk_switch_motion (GtkWidget *widget,
GdkEventMotion *event)
static void
gtk_switch_multipress_gesture_released (GtkGestureMultiPress *gesture,
gint n_press,
gdouble x,
gdouble y,
GtkSwitch *sw)
{
GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
/* if this is a direct toggle we don't handle motion */
if (priv->in_press)
return GDK_EVENT_PROPAGATE;
if (ABS (event->x - priv->drag_start) < priv->drag_threshold)
return GDK_EVENT_STOP;
if (event->state & GDK_BUTTON1_MASK)
{
gint position = event->x - priv->offset;
GtkAllocation allocation;
GtkStyleContext *context;
GtkStateFlags state;
GtkBorder padding;
gint width;
context = gtk_widget_get_style_context (widget);
state = gtk_widget_get_state_flags (widget);
gtk_style_context_save (context);
gtk_style_context_add_class (context, GTK_STYLE_CLASS_SLIDER);
gtk_style_context_get_padding (context, state, &padding);
gtk_style_context_restore (context);
gtk_widget_get_allocation (widget, &allocation);
width = allocation.width;
/* constrain the handle within the trough width */
if (position > (width / 2) - padding.right)
priv->handle_x = width / 2 - padding.right;
else if (position < padding.left)
priv->handle_x = 0;
else
priv->handle_x = position;
priv->is_dragging = TRUE;
/* we need to redraw the handle */
gtk_widget_queue_draw (widget);
return GDK_EVENT_STOP;
}
return GDK_EVENT_PROPAGATE;
}
static gboolean
gtk_switch_button_release (GtkWidget *widget,
GdkEventButton *event)
{
GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
GtkSwitchPrivate *priv = sw->priv;
GdkEventSequence *sequence;
GtkAllocation allocation;
/* Don't handle extra mouse buttons events, let them bubble up */
if (event->button > 5)
return GDK_EVENT_PROPAGATE;
gtk_widget_get_allocation (GTK_WIDGET (sw), &allocation);
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
if (priv->in_switch &&
gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
gtk_switch_set_active (sw, !priv->is_active);
}
static void
gtk_switch_pan_gesture_pan (GtkGesturePan *gesture,
GtkPanDirection direction,
gdouble offset,
GtkSwitch *sw)
{
GtkWidget *widget = GTK_WIDGET (sw);
GtkSwitchPrivate *priv = sw->priv;
GtkAllocation allocation;
GtkStyleContext *context;
GtkStateFlags state;
GtkBorder padding;
gint width, position;
if (direction == GTK_PAN_DIRECTION_LEFT)
offset = -offset;
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
position = priv->offset + offset;
context = gtk_widget_get_style_context (widget);
state = gtk_widget_get_state_flags (widget);
gtk_style_context_save (context);
gtk_style_context_add_class (context, GTK_STYLE_CLASS_SLIDER);
gtk_style_context_get_padding (context, state, &padding);
gtk_style_context_restore (context);
gtk_widget_get_allocation (widget, &allocation);
/* dragged toggles have a "soft" grab, so we don't care whether we
* are in the switch or not when the button is released; we do care
* for direct toggles, instead
*/
if (!priv->is_dragging && !priv->in_switch)
return GDK_EVENT_PROPAGATE;
width = allocation.width;
/* direct toggle */
if (priv->in_press)
/* constrain the handle within the trough width */
if (position > (width / 2) - padding.right)
priv->handle_x = width / 2 - padding.right;
else if (position < padding.left)
priv->handle_x = 0;
else
priv->handle_x = position;
/* we need to redraw the handle */
gtk_widget_queue_draw (widget);
}
static void
gtk_switch_pan_gesture_drag_end (GtkGestureDrag *gesture,
gdouble x,
gdouble y,
GtkSwitch *sw)
{
GtkSwitchPrivate *priv = sw->priv;
GdkEventSequence *sequence;
GtkAllocation allocation;
gboolean active = FALSE;
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
if (gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), sequence) == GTK_EVENT_SEQUENCE_CLAIMED)
{
priv->in_press = FALSE;
gtk_switch_set_active (GTK_SWITCH (widget), !priv->is_active);
return GDK_EVENT_STOP;
}
/* toggle the switch if the handle was clicked but a drag had not been
* initiated */
if (!priv->is_dragging && !priv->in_press)
{
gtk_switch_set_active (GTK_SWITCH (widget), !priv->is_active);
return GDK_EVENT_STOP;
}
/* dragged toggle */
if (priv->is_dragging)
{
priv->is_dragging = FALSE;
gtk_widget_get_allocation (GTK_WIDGET (sw), &allocation);
/* if half the handle passed the middle of the switch, then we
* consider it to be on
*/
if ((priv->handle_x + (allocation.width / 4)) >= (allocation.width / 2))
{
gtk_switch_set_active (GTK_SWITCH (widget), TRUE);
priv->handle_x = allocation.width / 2;
}
else
{
gtk_switch_set_active (GTK_SWITCH (widget), FALSE);
priv->handle_x = 0;
}
gtk_widget_queue_draw (widget);
return GDK_EVENT_STOP;
active = TRUE;
}
else if (!gtk_gesture_handles_sequence (priv->multipress_gesture, sequence))
active = priv->is_active;
else
return;
return GDK_EVENT_PROPAGATE;
if (active)
priv->handle_x = allocation.width / 2;
else
priv->handle_x = 0;
gtk_switch_set_active (sw, active);
gtk_widget_queue_draw (GTK_WIDGET (sw));
}
static gboolean
@ -593,7 +554,7 @@ gtk_switch_draw (GtkWidget *widget,
g_object_unref (layout);
if (priv->is_dragging)
if (gtk_gesture_is_recognized (priv->pan_gesture))
handle.x = x + priv->handle_x;
else if (priv->is_active)
handle.x = x + width - handle.width;
@ -869,9 +830,6 @@ gtk_switch_class_init (GtkSwitchClass *klass)
widget_class->map = gtk_switch_map;
widget_class->unmap = gtk_switch_unmap;
widget_class->draw = gtk_switch_draw;
widget_class->button_press_event = gtk_switch_button_press;
widget_class->button_release_event = gtk_switch_button_release;
widget_class->motion_notify_event = gtk_switch_motion;
widget_class->enter_notify_event = gtk_switch_enter;
widget_class->leave_notify_event = gtk_switch_leave;
@ -952,10 +910,33 @@ gtk_switch_class_init (GtkSwitchClass *klass)
static void
gtk_switch_init (GtkSwitch *self)
{
GtkGesture *gesture;
self->priv = gtk_switch_get_instance_private (self);
self->priv->use_action_appearance = TRUE;
gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
gesture = gtk_gesture_multi_press_new (GTK_WIDGET (self));
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE);
g_signal_connect (gesture, "pressed",
G_CALLBACK (gtk_switch_multipress_gesture_pressed), self);
g_signal_connect (gesture, "released",
G_CALLBACK (gtk_switch_multipress_gesture_released), self);
gtk_gesture_attach (gesture, GTK_PHASE_TARGET);
self->priv->multipress_gesture = gesture;
gesture = gtk_gesture_pan_new (GTK_WIDGET (self),
GTK_PAN_ORIENTATION_HORIZONTAL);
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE);
g_signal_connect (gesture, "pan",
G_CALLBACK (gtk_switch_pan_gesture_pan), self);
g_signal_connect (gesture, "drag-end",
G_CALLBACK (gtk_switch_pan_gesture_drag_end), self);
gtk_gesture_attach (gesture, GTK_PHASE_TARGET);
self->priv->pan_gesture = gesture;
}
/**