[gtk+] GtkAdjustment: Animated updates



commit a090f5892248612089d3d655dcb746c755ba962b
Author: Matthias Clasen <mclasen redhat com>
Date:   Mon Jun 30 18:09:50 2014 -0400

    GtkAdjustment: Animated updates
    
    Add a private API that lets widget opt-in to animated updates of
    the adjustment value. When enabled, all calls to
    gtk_adjustment_set_value will smoothly transition from the old
    value to the new value, using a fixed easing function and a
    configurable duration. The animation is tied to the frame clock
    of the widget.
    
    By implementing this in GtkAdjustment, we can enable animation
    for both scrollbars and keybindings, which are often implemented
    in the child widget of the scrolled window.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=732376

 gtk/Makefile.am            |    1 +
 gtk/gtkadjustment.c        |  149 ++++++++++++++++++++++++++++++++++++++++++--
 gtk/gtkadjustmentprivate.h |   34 ++++++++++
 3 files changed, 178 insertions(+), 6 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 5fafcab..1d4b624 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -498,6 +498,7 @@ gtk_private_h_sources =             \
        gtkactionmuxer.h        \
        gtkactionobserver.h     \
        gtkactionobservable.h   \
+       gtkadjustmentprivate.h  \
        gtkapplicationprivate.h \
        gtkaccelgroupprivate.h  \
        gtkaccelmapprivate.h    \
diff --git a/gtk/gtkadjustment.c b/gtk/gtkadjustment.c
index 9a5087d..a124bac 100644
--- a/gtk/gtkadjustment.c
+++ b/gtk/gtkadjustment.c
@@ -57,6 +57,15 @@ struct _GtkAdjustmentPrivate {
   gdouble step_increment;
   gdouble page_increment;
   gdouble page_size;
+
+  gdouble source;
+  gdouble target;
+
+  guint duration;
+  gint64 start_time;
+  gint64 end_time;
+  guint tick_id;
+  GdkFrameClock *clock;
 };
 
 enum
@@ -97,10 +106,25 @@ static guint64 adjustment_changed_stamp = 0; /* protected by global gdk lock */
 G_DEFINE_TYPE_WITH_PRIVATE (GtkAdjustment, gtk_adjustment, G_TYPE_INITIALLY_UNOWNED)
 
 static void
+gtk_adjustment_finalize (GObject *object)
+{
+  GtkAdjustment *adjustment = GTK_ADJUSTMENT (object);
+  GtkAdjustmentPrivate *priv = adjustment->priv;
+
+  if (priv->tick_id)
+    g_signal_handler_disconnect (priv->clock, priv->tick_id);
+  if (priv->clock)
+    g_object_unref (priv->clock);
+
+  G_OBJECT_CLASS (gtk_adjustment_parent_class)->finalize (object);
+}
+
+static void
 gtk_adjustment_class_init (GtkAdjustmentClass *class)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
 
+  gobject_class->finalize                    = gtk_adjustment_finalize;
   gobject_class->set_property                = gtk_adjustment_set_property;
   gobject_class->get_property                = gtk_adjustment_get_property;
   gobject_class->dispatch_properties_changed = gtk_adjustment_dispatch_properties_changed;
@@ -404,6 +428,95 @@ gtk_adjustment_get_value (GtkAdjustment *adjustment)
   return adjustment->priv->value;
 }
 
+static void
+adjustment_set_value (GtkAdjustment *adjustment,
+                      gdouble        value)
+{
+  if (value != adjustment->priv->value)
+    {
+      adjustment->priv->value = value;
+      gtk_adjustment_value_changed (adjustment);
+    }
+}
+
+/* From clutter-easing.c, based on Robert Penner's
+ * infamous easing equations, MIT license.
+ */
+static gdouble
+ease_out_cubic (gdouble t)
+{
+  gdouble p = t - 1;
+
+  return p * p * p + 1;
+}
+
+static gboolean
+gtk_adjustment_animate_step (GtkAdjustment *adjustment,
+                             gint64         now)
+{
+  GtkAdjustmentPrivate *priv = adjustment->priv;
+
+  if (now < priv->end_time)
+    {
+      gdouble t;
+
+      t = (now - priv->start_time) / (gdouble) (priv->end_time - priv->start_time);
+      t = ease_out_cubic (t);
+      adjustment_set_value (adjustment, priv->source + t * (priv->target - priv->source));
+
+      return TRUE;
+    }
+  else
+    {
+      adjustment_set_value (adjustment, priv->target);
+
+      return FALSE;
+    }
+}
+
+static void
+gtk_adjustment_on_frame_clock_update (GdkFrameClock *clock, 
+                                      GtkAdjustment *adjustment)
+{
+  GtkAdjustmentPrivate *priv = adjustment->priv;
+  gint64 now;
+
+  now = gdk_frame_clock_get_frame_time (clock);
+  if (!gtk_adjustment_animate_step (adjustment, now))
+    {
+      g_signal_handler_disconnect (priv->clock, priv->tick_id);
+      priv->tick_id = 0;
+      gdk_frame_clock_end_updating (priv->clock);
+    }
+}
+
+static void
+gtk_adjustment_maybe_animate (GtkAdjustment *adjustment,
+                              gdouble        target)
+{
+  GtkAdjustmentPrivate *priv = adjustment->priv;
+
+  if (priv->target == target)
+    return;
+
+  priv->target = target;
+  
+  if (priv->duration != 0 && priv->clock != NULL)
+    {
+      priv->source = priv->value;
+      priv->start_time = gdk_frame_clock_get_frame_time (priv->clock);
+      priv->end_time = priv->start_time + 1000 * priv->duration;
+      if (priv->tick_id == 0)
+        {
+          priv->tick_id = g_signal_connect (priv->clock, "update",
+                                            G_CALLBACK (gtk_adjustment_on_frame_clock_update), adjustment);
+          gdk_frame_clock_begin_updating (priv->clock);
+        }
+    }
+  else
+    adjustment_set_value (adjustment, target);
+}
+
 /**
  * gtk_adjustment_set_value:
  * @adjustment: a #GtkAdjustment.
@@ -432,12 +545,7 @@ gtk_adjustment_set_value (GtkAdjustment *adjustment,
   value = MIN (value, priv->upper - priv->page_size);
   value = MAX (value, priv->lower);
 
-  if (value != priv->value)
-    {
-      priv->value = value;
-
-      gtk_adjustment_value_changed (adjustment);
-    }
+  gtk_adjustment_maybe_animate (adjustment, value);
 }
 
 /**
@@ -847,3 +955,32 @@ gtk_adjustment_get_minimum_increment (GtkAdjustment *adjustment)
   return minimum_increment;
 }
 
+void
+gtk_adjustment_enable_animation (GtkAdjustment *adjustment,
+                                 GdkFrameClock *clock,
+                                 guint          duration)
+{
+  GtkAdjustmentPrivate *priv = adjustment->priv;
+
+  if (priv->clock != clock)
+    {
+      if (priv->tick_id)
+        {
+          adjustment_set_value (adjustment, priv->target);
+
+          g_signal_handler_disconnect (priv->clock, priv->tick_id);
+          priv->tick_id = 0;
+          gdk_frame_clock_end_updating (priv->clock);
+        }
+
+      if (priv->clock)
+        g_object_unref (priv->clock);
+
+      priv->clock = clock;
+
+      if (priv->clock)
+        g_object_ref (priv->clock);
+    }
+
+  priv->duration = duration; 
+}
diff --git a/gtk/gtkadjustmentprivate.h b/gtk/gtkadjustmentprivate.h
new file mode 100644
index 0000000..bc9c810
--- /dev/null
+++ b/gtk/gtkadjustmentprivate.h
@@ -0,0 +1,34 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2014 Red Hat, Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_ADJUSTMENT_PRIVATE_H__
+#define __GTK_ADJUSTMENT_PRIVATE_H__
+
+
+#include <gtk/gtkadjustment.h>
+
+
+G_BEGIN_DECLS
+
+void gtk_adjustment_enable_animation (GtkAdjustment *adjustment,
+                                      GdkFrameClock *clock,
+                                      guint          duration);
+
+G_END_DECLS
+
+
+#endif /* __GTK_ADJUSTMENT_PRIVATE_H__ */


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