[gtk+] popover: Add show/hide transitions



commit cff1694c99227dbf0b52525d7436fc4771859e00
Author: Carlos Garnacho <carlosg gnome org>
Date:   Fri Jan 9 16:10:29 2015 +0100

    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

 docs/reference/gtk/gtk3-sections.txt |    2 +
 gtk/gtkpopover.c                     |  307 ++++++++++++++++++++++++++++++++--
 gtk/gtkpopover.h                     |    6 +
 3 files changed, 298 insertions(+), 17 deletions(-)
---
diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt
index d143278..bcd3547 100644
--- a/docs/reference/gtk/gtk3-sections.txt
+++ b/docs/reference/gtk/gtk3-sections.txt
@@ -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>
diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c
index 4d8b175..97d13aa 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)
 {
diff --git a/gtk/gtkpopover.h b/gtk/gtkpopover.h
index c96b97d..21c47fa 100644
--- a/gtk/gtkpopover.h
+++ b/gtk/gtkpopover.h
@@ -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__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]