diff options
author | Carlos Garnacho <carlosg@gnome.org> | 2015-01-09 16:10:29 +0100 |
---|---|---|
committer | Carlos Garnacho <carlosg@gnome.org> | 2015-02-20 14:44:17 +0100 |
commit | cff1694c99227dbf0b52525d7436fc4771859e00 (patch) | |
tree | d371496dd2c56e97c33d739bf008f9fd1304e109 /gtk/gtkpopover.c | |
parent | 3a671994d4f4de61a48df6198353a188b7862baa (diff) | |
download | gtk+-cff1694c99227dbf0b52525d7436fc4771859e00.tar.gz |
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
Diffstat (limited to 'gtk/gtkpopover.c')
-rw-r--r-- | gtk/gtkpopover.c | 307 |
1 files changed, 290 insertions, 17 deletions
diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c index 4d8b175d52..97d13aa861 100644 --- a/gtk/gtkpopover.c +++ b/gtk/gtkpopover.c @@ -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_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); 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) { |