Commit cff1694c authored by Carlos Garnacho's avatar Carlos Garnacho

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
parent 3a671994
...@@ -7965,6 +7965,8 @@ gtk_popover_set_position ...@@ -7965,6 +7965,8 @@ gtk_popover_set_position
gtk_popover_get_position gtk_popover_get_position
gtk_popover_set_modal gtk_popover_set_modal
gtk_popover_get_modal gtk_popover_get_modal
gtk_popover_set_transitions_enabled
gtk_popover_get_transitions_enabled
<SUBSECTION Private> <SUBSECTION Private>
gtk_popover_get_type gtk_popover_get_type
</SECTION> </SECTION>
......
...@@ -103,8 +103,10 @@ ...@@ -103,8 +103,10 @@
#include "wayland/gdkwayland.h" #include "wayland/gdkwayland.h"
#endif #endif
#define TAIL_GAP_WIDTH 24 #define TAIL_GAP_WIDTH 24
#define TAIL_HEIGHT 12 #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) #define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM)
...@@ -112,7 +114,8 @@ enum { ...@@ -112,7 +114,8 @@ enum {
PROP_RELATIVE_TO = 1, PROP_RELATIVE_TO = 1,
PROP_POINTING_TO, PROP_POINTING_TO,
PROP_POSITION, PROP_POSITION,
PROP_MODAL PROP_MODAL,
PROP_TRANSITIONS_ENABLED
}; };
enum { enum {
...@@ -120,6 +123,13 @@ enum { ...@@ -120,6 +123,13 @@ enum {
N_SIGNALS N_SIGNALS
}; };
enum {
STATE_SHOWING,
STATE_SHOWN,
STATE_HIDING,
STATE_HIDDEN
};
struct _GtkPopoverPrivate struct _GtkPopoverPrivate
{ {
GtkWidget *widget; GtkWidget *widget;
...@@ -144,6 +154,12 @@ struct _GtkPopoverPrivate ...@@ -144,6 +154,12 @@ struct _GtkPopoverPrivate
guint button_pressed : 1; guint button_pressed : 1;
guint apply_shape : 1; guint apply_shape : 1;
guint grab_notify_blocked : 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; static GQuark quark_widget_popovers = 0;
...@@ -151,6 +167,8 @@ static guint signals[N_SIGNALS] = { 0 }; ...@@ -151,6 +167,8 @@ static guint signals[N_SIGNALS] = { 0 };
static void gtk_popover_update_relative_to (GtkPopover *popover, static void gtk_popover_update_relative_to (GtkPopover *popover,
GtkWidget *relative_to); GtkWidget *relative_to);
static void gtk_popover_set_state (GtkPopover *popover,
guint state);
G_DEFINE_TYPE_WITH_PRIVATE (GtkPopover, gtk_popover, GTK_TYPE_BIN) G_DEFINE_TYPE_WITH_PRIVATE (GtkPopover, gtk_popover, GTK_TYPE_BIN)
...@@ -165,6 +183,8 @@ gtk_popover_init (GtkPopover *popover) ...@@ -165,6 +183,8 @@ gtk_popover_init (GtkPopover *popover)
popover->priv = gtk_popover_get_instance_private (popover); popover->priv = gtk_popover_get_instance_private (popover);
popover->priv->modal = TRUE; popover->priv->modal = TRUE;
popover->priv->apply_shape = TRUE; popover->priv->apply_shape = TRUE;
popover->priv->tick_id = 0;
popover->priv->transitions_enabled = TRUE;
context = gtk_widget_get_style_context (widget); context = gtk_widget_get_style_context (widget);
gtk_style_context_add_class (context, GTK_STYLE_CLASS_BACKGROUND); gtk_style_context_add_class (context, GTK_STYLE_CLASS_BACKGROUND);
...@@ -195,6 +215,10 @@ gtk_popover_set_property (GObject *object, ...@@ -195,6 +215,10 @@ gtk_popover_set_property (GObject *object,
gtk_popover_set_modal (GTK_POPOVER (object), gtk_popover_set_modal (GTK_POPOVER (object),
g_value_get_boolean (value)); g_value_get_boolean (value));
break; break;
case PROP_TRANSITIONS_ENABLED:
gtk_popover_set_transitions_enabled (GTK_POPOVER (object),
g_value_get_boolean (value));
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
} }
...@@ -222,11 +246,27 @@ gtk_popover_get_property (GObject *object, ...@@ -222,11 +246,27 @@ gtk_popover_get_property (GObject *object,
case PROP_MODAL: case PROP_MODAL:
g_value_set_boolean (value, priv->modal); g_value_set_boolean (value, priv->modal);
break; break;
case PROP_TRANSITIONS_ENABLED:
g_value_set_boolean (value, priv->transitions_enabled);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 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 static void
gtk_popover_finalize (GObject *object) gtk_popover_finalize (GObject *object)
{ {
...@@ -263,8 +303,6 @@ gtk_popover_dispose (GObject *object) ...@@ -263,8 +303,6 @@ gtk_popover_dispose (GObject *object)
GtkPopover *popover = GTK_POPOVER (object); GtkPopover *popover = GTK_POPOVER (object);
GtkPopoverPrivate *priv = popover->priv; GtkPopoverPrivate *priv = popover->priv;
gtk_widget_set_visible (GTK_WIDGET (object), FALSE);
if (priv->window) if (priv->window)
_gtk_window_remove_popover (priv->window, GTK_WIDGET (object)); _gtk_window_remove_popover (priv->window, GTK_WIDGET (object));
...@@ -421,25 +459,126 @@ gtk_popover_apply_modality (GtkPopover *popover, ...@@ -421,25 +459,126 @@ gtk_popover_apply_modality (GtkPopover *popover,
if (priv->prev_focus_widget && if (priv->prev_focus_widget &&
gtk_widget_is_drawable (priv->prev_focus_widget)) gtk_widget_is_drawable (priv->prev_focus_widget))
gtk_widget_grab_focus (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)); gtk_widget_grab_focus (GTK_WIDGET (priv->window));
popover_unset_prev_focus (popover); 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 static void
gtk_popover_map (GtkWidget *widget) gtk_popover_start_transition (GtkPopover *popover)
{ {
GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv; 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)
{
GTK_WIDGET_CLASS (gtk_popover_parent_class)->map (widget); GTK_WIDGET_CLASS (gtk_popover_parent_class)->map (widget);
gdk_window_show (gtk_widget_get_window (widget)); gdk_window_show (gtk_widget_get_window (widget));
gtk_popover_update_position (GTK_POPOVER (widget)); gtk_popover_update_position (GTK_POPOVER (widget));
if (priv->modal)
gtk_popover_apply_modality (GTK_POPOVER (widget), TRUE);
} }
static void static void
...@@ -449,13 +588,8 @@ gtk_popover_unmap (GtkWidget *widget) ...@@ -449,13 +588,8 @@ gtk_popover_unmap (GtkWidget *widget)
priv->button_pressed = FALSE; priv->button_pressed = FALSE;
if (priv->modal)
gtk_popover_apply_modality (GTK_POPOVER (widget), FALSE);
gdk_window_hide (gtk_widget_get_window (widget)); gdk_window_hide (gtk_widget_get_window (widget));
GTK_WIDGET_CLASS (gtk_popover_parent_class)->unmap (widget); GTK_WIDGET_CLASS (gtk_popover_parent_class)->unmap (widget);
g_signal_emit (widget, signals[CLOSED], 0);
} }
static void static void
...@@ -853,6 +987,22 @@ gtk_popover_update_position (GtkPopover *popover) ...@@ -853,6 +987,22 @@ gtk_popover_update_position (GtkPopover *popover)
priv->final_position = pos; 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, _gtk_window_set_popover_position (priv->window, widget,
priv->final_position, &rect); priv->final_position, &rect);
...@@ -1281,8 +1431,12 @@ gtk_popover_key_press (GtkWidget *widget, ...@@ -1281,8 +1431,12 @@ gtk_popover_key_press (GtkWidget *widget,
static void static void
gtk_popover_grab_focus (GtkWidget *widget) gtk_popover_grab_focus (GtkWidget *widget)
{ {
GtkPopoverPrivate *priv = GTK_POPOVER (widget)->priv;
GtkWidget *child; GtkWidget *child;
if (!priv->visible)
return;
/* Focus the first natural child */ /* Focus the first natural child */
child = gtk_bin_get_child (GTK_BIN (widget)); child = gtk_bin_get_child (GTK_BIN (widget));
...@@ -1295,6 +1449,10 @@ gtk_popover_focus (GtkWidget *widget, ...@@ -1295,6 +1449,10 @@ gtk_popover_focus (GtkWidget *widget,
GtkDirectionType direction) GtkDirectionType direction)
{ {
GtkPopover *popover = GTK_POPOVER (widget); 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)) if (!GTK_WIDGET_CLASS (gtk_popover_parent_class)->focus (widget, direction))
{ {
...@@ -1319,6 +1477,53 @@ gtk_popover_focus (GtkWidget *widget, ...@@ -1319,6 +1477,53 @@ gtk_popover_focus (GtkWidget *widget,
return TRUE; 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 static void
gtk_popover_class_init (GtkPopoverClass *klass) gtk_popover_class_init (GtkPopoverClass *klass)
...@@ -1345,6 +1550,8 @@ 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->key_press_event = gtk_popover_key_press;
widget_class->grab_focus = gtk_popover_grab_focus; widget_class->grab_focus = gtk_popover_grab_focus;
widget_class->focus = gtk_popover_focus; widget_class->focus = gtk_popover_focus;
widget_class->show = gtk_popover_show;
widget_class->hide = gtk_popover_hide;
/** /**
* GtkPopover:relative-to: * GtkPopover:relative-to:
...@@ -1405,6 +1612,21 @@ gtk_popover_class_init (GtkPopoverClass *klass) ...@@ -1405,6 +1612,21 @@ gtk_popover_class_init (GtkPopoverClass *klass)
TRUE, TRUE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); 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] = signals[CLOSED] =
g_signal_new (I_("closed"), g_signal_new (I_("closed"),
G_TYPE_FROM_CLASS (object_class), G_TYPE_FROM_CLASS (object_class),
...@@ -1514,7 +1736,12 @@ static void ...@@ -1514,7 +1736,12 @@ static void
_gtk_popover_parent_unmap (GtkWidget *widget, _gtk_popover_parent_unmap (GtkWidget *widget,
GtkPopover *popover) 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 static void
...@@ -1974,6 +2201,52 @@ gtk_popover_get_modal (GtkPopover *popover) ...@@ -1974,6 +2201,52 @@ gtk_popover_get_modal (GtkPopover *popover)
return popover->priv->modal; 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 static void
back_to_main (GtkWidget *popover) back_to_main (GtkWidget *popover)
{ {
......
...@@ -97,6 +97,12 @@ void gtk_popover_bind_model (GtkPopover *popover, ...@@ -97,6 +97,12 @@ void gtk_popover_bind_model (GtkPopover *popover,
GMenuModel *model, GMenuModel *model,
const gchar *action_namespace); 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 G_END_DECLS
#endif /* __GTK_POPOVER_H__ */ #endif /* __GTK_POPOVER_H__ */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment