popover: Add show/hide transitions

These have the same visual effect and timing than the gnome-shell ones.
During the hide animation, the popover has been made to take focus
elsewhere, and refuse to take any pointer/keyboard input until the popover
is shown again.

This has been based on work from Timm Bäder.

https://bugzilla.gnome.org/show_bug.cgi?id=741405
This commit is contained in:
Carlos Garnacho 2015-01-09 16:10:29 +01:00
parent 3a671994d4
commit cff1694c99
3 changed files with 298 additions and 17 deletions

View File

@ -7965,6 +7965,8 @@ gtk_popover_set_position
gtk_popover_get_position
gtk_popover_set_modal
gtk_popover_get_modal
gtk_popover_set_transitions_enabled
gtk_popover_get_transitions_enabled
<SUBSECTION Private>
gtk_popover_get_type
</SECTION>

View File

@ -103,8 +103,10 @@
#include "wayland/gdkwayland.h"
#endif
#define TAIL_GAP_WIDTH 24
#define TAIL_HEIGHT 12
#define TAIL_GAP_WIDTH 24
#define TAIL_HEIGHT 12
#define TRANSITION_DIFF 20
#define TRANSITION_DURATION 330 * 1000
#define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM)
@ -112,7 +114,8 @@ enum {
PROP_RELATIVE_TO = 1,
PROP_POINTING_TO,
PROP_POSITION,
PROP_MODAL
PROP_MODAL,
PROP_TRANSITIONS_ENABLED
};
enum {
@ -120,6 +123,13 @@ enum {
N_SIGNALS
};
enum {
STATE_SHOWING,
STATE_SHOWN,
STATE_HIDING,
STATE_HIDDEN
};
struct _GtkPopoverPrivate
{
GtkWidget *widget;
@ -144,6 +154,12 @@ struct _GtkPopoverPrivate
guint button_pressed : 1;
guint apply_shape : 1;
guint grab_notify_blocked : 1;
guint transitions_enabled : 1;
guint state : 2;
guint visible : 1;
gint64 start_time;
gint transition_diff;
guint tick_id;
};
static GQuark quark_widget_popovers = 0;
@ -151,6 +167,8 @@ static guint signals[N_SIGNALS] = { 0 };
static void gtk_popover_update_relative_to (GtkPopover *popover,
GtkWidget *relative_to);
static void gtk_popover_set_state (GtkPopover *popover,
guint state);
G_DEFINE_TYPE_WITH_PRIVATE (GtkPopover, gtk_popover, GTK_TYPE_BIN)
@ -165,6 +183,8 @@ gtk_popover_init (GtkPopover *popover)
popover->priv = gtk_popover_get_instance_private (popover);
popover->priv->modal = TRUE;
popover->priv->apply_shape = TRUE;
popover->priv->tick_id = 0;
popover->priv->transitions_enabled = TRUE;
context = gtk_widget_get_style_context (widget);
gtk_style_context_add_class (context, GTK_STYLE_CLASS_BACKGROUND);
@ -195,6 +215,10 @@ gtk_popover_set_property (GObject *object,
gtk_popover_set_modal (GTK_POPOVER (object),
g_value_get_boolean (value));
break;
case PROP_TRANSITIONS_ENABLED:
gtk_popover_set_transitions_enabled (GTK_POPOVER (object),
g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@ -222,11 +246,27 @@ gtk_popover_get_property (GObject *object,
case PROP_MODAL:
g_value_set_boolean (value, priv->modal);
break;
case PROP_TRANSITIONS_ENABLED:
g_value_set_boolean (value, priv->transitions_enabled);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static gboolean
transitions_enabled (GtkPopover *popover)
{
GtkPopoverPrivate *priv = popover->priv;
gboolean animations_enabled;
g_object_get (gtk_widget_get_settings (GTK_WIDGET (popover)),
"gtk-enable-animations", &animations_enabled,
NULL);
return animations_enabled && priv->transitions_enabled;
}
static void
gtk_popover_finalize (GObject *object)
{
@ -263,8 +303,6 @@ gtk_popover_dispose (GObject *object)
GtkPopover *popover = GTK_POPOVER (object);
GtkPopoverPrivate *priv = popover->priv;
gtk_widget_set_visible (GTK_WIDGET (object), FALSE);
if (priv->window)
_gtk_window_remove_popover (priv->window, GTK_WIDGET (object));
@ -421,25 +459,126 @@ gtk_popover_apply_modality (GtkPopover *popover,
if (priv->prev_focus_widget &&
gtk_widget_is_drawable (priv->prev_focus_widget))
gtk_widget_grab_focus (priv->prev_focus_widget);
else
else if (priv->window)
gtk_widget_grab_focus (GTK_WIDGET (priv->window));
popover_unset_prev_focus (popover);
}
}
/* From clutter-easing.c, based on Robert Penner's
* infamous easing equations, MIT license.
*/
static double
ease_out_cubic (double t)
{
double p = t - 1;
return p * p * p + 1;
}
static gboolean
show_animate_cb (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer user_data)
{
GtkPopover *popover = GTK_POPOVER (widget);
GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
gint64 now = gdk_frame_clock_get_frame_time (frame_clock);
gdouble t;
if (now < (priv->start_time + TRANSITION_DURATION))
t = (now - priv->start_time) / (gdouble) (TRANSITION_DURATION);
else
t = 1.0;
t = ease_out_cubic (t);
if (priv->state == STATE_SHOWING)
{
priv->transition_diff = TRANSITION_DIFF - (TRANSITION_DIFF * t);
gtk_widget_set_opacity (widget, t);
}
else if (priv->state == STATE_HIDING)
{
priv->transition_diff = -TRANSITION_DIFF * t;
gtk_widget_set_opacity (widget, 1.0 - t);
}
gtk_widget_queue_resize (GTK_WIDGET (popover));
if (t >= 1.0)
{
if (priv->state == STATE_SHOWING)
{
gtk_popover_set_state (popover, STATE_SHOWN);
if (!priv->visible)
gtk_popover_set_state (popover, STATE_HIDING);
}
else
gtk_popover_set_state (popover, STATE_HIDDEN);
return FALSE;
}
else
return TRUE;
}
static void
gtk_popover_start_transition (GtkPopover *popover)
{
GtkPopoverPrivate *priv = popover->priv;
GdkFrameClock *clock;
if (priv->tick_id != 0)
return;
clock = gtk_widget_get_frame_clock (GTK_WIDGET (popover));
priv->start_time = gdk_frame_clock_get_frame_time (clock);
priv->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (popover),
show_animate_cb,
popover, NULL);
}
static void
gtk_popover_set_state (GtkPopover *popover,
guint state)
{
GtkPopoverPrivate *priv = popover->priv;
if (!transitions_enabled (popover) ||
!gtk_widget_get_realized (GTK_WIDGET (popover)))
{
if (state == STATE_SHOWING)
state = STATE_SHOWN;
else if (state == STATE_HIDING)
state = STATE_HIDDEN;
}
priv->state = state;
if (state == STATE_SHOWING || state == STATE_HIDING)
gtk_popover_start_transition (popover);
else
{
if (priv->tick_id)
{
gtk_widget_remove_tick_callback (GTK_WIDGET (popover), priv->tick_id);
priv->tick_id = 0;
}
gtk_widget_set_visible (GTK_WIDGET (popover), state == STATE_SHOWN);
}
}
static void
gtk_popover_map (GtkWidget *widget)
{
GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
GTK_WIDGET_CLASS (gtk_popover_parent_class)->map (widget);
gdk_window_show (gtk_widget_get_window (widget));
gtk_popover_update_position (GTK_POPOVER (widget));
if (priv->modal)
gtk_popover_apply_modality (GTK_POPOVER (widget), TRUE);
}
static void
@ -449,13 +588,8 @@ gtk_popover_unmap (GtkWidget *widget)
priv->button_pressed = FALSE;
if (priv->modal)
gtk_popover_apply_modality (GTK_POPOVER (widget), FALSE);
gdk_window_hide (gtk_widget_get_window (widget));
GTK_WIDGET_CLASS (gtk_popover_parent_class)->unmap (widget);
g_signal_emit (widget, signals[CLOSED], 0);
}
static void
@ -853,6 +987,22 @@ gtk_popover_update_position (GtkPopover *popover)
priv->final_position = pos;
}
switch (priv->final_position)
{
case GTK_POS_TOP:
rect.y += priv->transition_diff;
break;
case GTK_POS_BOTTOM:
rect.y -= priv->transition_diff;
break;
case GTK_POS_LEFT:
rect.x += priv->transition_diff;
break;
case GTK_POS_RIGHT:
rect.x -= priv->transition_diff;
break;
}
_gtk_window_set_popover_position (priv->window, widget,
priv->final_position, &rect);
@ -1281,8 +1431,12 @@ gtk_popover_key_press (GtkWidget *widget,
static void
gtk_popover_grab_focus (GtkWidget *widget)
{
GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
GtkWidget *child;
if (!priv->visible)
return;
/* Focus the first natural child */
child = gtk_bin_get_child (GTK_BIN (widget));
@ -1295,6 +1449,10 @@ gtk_popover_focus (GtkWidget *widget,
GtkDirectionType direction)
{
GtkPopover *popover = GTK_POPOVER (widget);
GtkPopoverPrivate *priv = popover->priv;
if (!priv->visible)
return FALSE;
if (!GTK_WIDGET_CLASS (gtk_popover_parent_class)->focus (widget, direction))
{
@ -1319,6 +1477,53 @@ gtk_popover_focus (GtkWidget *widget,
return TRUE;
}
static void
gtk_popover_show (GtkWidget *widget)
{
GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
priv->visible = TRUE;
GTK_WIDGET_CLASS (gtk_popover_parent_class)->show (widget);
if (priv->modal)
gtk_popover_apply_modality (GTK_POPOVER (widget), TRUE);
gtk_popover_set_state (GTK_POPOVER (widget), STATE_SHOWING);
if (gtk_widget_get_realized (widget))
gdk_window_input_shape_combine_region (gtk_widget_get_parent_window (widget),
NULL, 0, 0);
}
static void
gtk_popover_hide (GtkWidget *widget)
{
GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
cairo_region_t *region;
if (priv->visible)
{
priv->visible = FALSE;
g_signal_emit (widget, signals[CLOSED], 0);
if (priv->modal)
gtk_popover_apply_modality (GTK_POPOVER (widget), FALSE);
}
if (gtk_widget_get_realized (widget))
{
region = cairo_region_create ();
gdk_window_input_shape_combine_region (gtk_widget_get_parent_window (widget),
region, 0, 0);
cairo_region_destroy (region);
}
if (!priv->window || priv->state == STATE_HIDDEN)
GTK_WIDGET_CLASS (gtk_popover_parent_class)->hide (widget);
else if (priv->state != STATE_SHOWING)
gtk_popover_set_state (GTK_POPOVER (widget), STATE_HIDING);
}
static void
gtk_popover_class_init (GtkPopoverClass *klass)
@ -1345,6 +1550,8 @@ gtk_popover_class_init (GtkPopoverClass *klass)
widget_class->key_press_event = gtk_popover_key_press;
widget_class->grab_focus = gtk_popover_grab_focus;
widget_class->focus = gtk_popover_focus;
widget_class->show = gtk_popover_show;
widget_class->hide = gtk_popover_hide;
/**
* GtkPopover:relative-to:
@ -1405,6 +1612,21 @@ gtk_popover_class_init (GtkPopoverClass *klass)
TRUE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
/**
* GtkPopover:transitions-enabled
*
* Whether show/hide transitions are enabled for this popover.
*
* Since: 3.16
*/
g_object_class_install_property (object_class,
PROP_TRANSITIONS_ENABLED,
g_param_spec_boolean ("transitions-enabled",
P_("Transitions enabled"),
P_("Whether show/hide transitions are enabled or not"),
TRUE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
signals[CLOSED] =
g_signal_new (I_("closed"),
G_TYPE_FROM_CLASS (object_class),
@ -1514,7 +1736,12 @@ static void
_gtk_popover_parent_unmap (GtkWidget *widget,
GtkPopover *popover)
{
gtk_widget_unmap (GTK_WIDGET (popover));
GtkPopoverPrivate *priv = popover->priv;
if (priv->state == STATE_SHOWING)
priv->visible = FALSE;
else if (priv->state == STATE_SHOWN)
gtk_popover_set_state (popover, STATE_HIDING);
}
static void
@ -1974,6 +2201,52 @@ gtk_popover_get_modal (GtkPopover *popover)
return popover->priv->modal;
}
/**
* gtk_popover_set_transitions_enabled:
* @popover: a #GtkPopover
* @transitions_enabled: Whether transitions are enabled
*
* Sets whether show/hide transitions are enabled on this popover
*
* Since: 3.16
*/
void
gtk_popover_set_transitions_enabled (GtkPopover *popover,
gboolean transitions_enabled)
{
GtkPopoverPrivate *priv = popover->priv;
g_return_if_fail (GTK_IS_POPOVER (popover));
transitions_enabled = !!transitions_enabled;
if (priv->transitions_enabled == transitions_enabled)
return;
priv->transitions_enabled = transitions_enabled;
g_object_notify (G_OBJECT (popover), "transitions-enabled");
}
/**
* gtk_popover_get_transitions_enabled:
* @popover: a #GtkPopover
*
* Returns whether show/hide transitions are enabled on this popover.
*
* Returns: #TRUE if the show and hide transitions of the given
* popover are enabled, #FALSE otherwise.
*
* Since: 3.16
*/
gboolean
gtk_popover_get_transitions_enabled (GtkPopover *popover)
{
g_return_val_if_fail (GTK_IS_POPOVER (popover), FALSE);
return popover->priv->transitions_enabled;
}
static void
back_to_main (GtkWidget *popover)
{

View File

@ -97,6 +97,12 @@ void gtk_popover_bind_model (GtkPopover *popover,
GMenuModel *model,
const gchar *action_namespace);
GDK_AVAILABLE_IN_3_16
void gtk_popover_set_transitions_enabled (GtkPopover *popover,
gboolean transitions_enabled);
GDK_AVAILABLE_IN_3_16
gboolean gtk_popover_get_transitions_enabled (GtkPopover *popover);
G_END_DECLS
#endif /* __GTK_POPOVER_H__ */