demos: Add an animated paintable demo
authorBenjamin Otte <otte@redhat.com>
Tue, 27 Feb 2018 02:09:33 +0000 (03:09 +0100)
committerBenjamin Otte <otte@redhat.com>
Sun, 18 Mar 2018 20:01:23 +0000 (21:01 +0100)
This builds on the paintable demo.

demos/gtk-demo/demo.gresource.xml
demos/gtk-demo/meson.build
demos/gtk-demo/paintable.c
demos/gtk-demo/paintable.h [new file with mode: 0644]
demos/gtk-demo/paintable_animated.c [new file with mode: 0644]

index 06b08b6f9c7731af0c479c751e32978a2fee2c03..81519f1c371bca31dd7ccfdb3499ad5a95b935a6 100644 (file)
     <file>overlay2.c</file>
     <file>pagesetup.c</file>
     <file>paintable.c</file>
+    <file>paintable_animated.c</file>
     <file>panes.c</file>
     <file>pickers.c</file>
     <file>pixbufs.c</file>
index ac9f5e00007be2fa189471e84bbafad6e02ddaca..5d3a318bd15d0a45d4c0874be784e7826636060b 100644 (file)
@@ -46,6 +46,7 @@ demos = files([
   'overlay.c',
   'overlay2.c',
   'paintable.c',
+  'paintable_animated.c',
   'panes.c',
   'pickers.c',
   'pixbufs.c',
index 8dd013f45165fe18dedb5b900b6eecb360451725..da7e22d20825e619c51e0a0005c1703803b4c837 100644 (file)
@@ -13,6 +13,8 @@
 
 #include <gtk/gtk.h>
 
+#include "paintable.h"
+
 static GtkWidget *window = NULL;
 
 /* First, add the boilerplate for the object itself.
@@ -21,14 +23,16 @@ static GtkWidget *window = NULL;
 #define GTK_TYPE_NUCLEAR_ICON (gtk_nuclear_icon_get_type ())
 G_DECLARE_FINAL_TYPE (GtkNuclearIcon, gtk_nuclear_icon, GTK, NUCLEAR_ICON, GObject)
 
-GdkPaintable *gtk_nuclear_icon_new (void);
-
-/* Declare the struct. We have no custom data, so it's very
- * bare.
- */
+/* Declare the struct. */
 struct _GtkNuclearIcon
 {
   GObject parent_instance;
+
+  /* We store this rotation value here.
+   * We are not doing with it here, but it will come in
+   * very useful in the followup demos.
+   */
+  double rotation;
 };
 
 struct _GtkNuclearIconClass
@@ -36,21 +40,20 @@ struct _GtkNuclearIconClass
   GObjectClass parent_class;
 };
 
-/* Here, we implement the functionality required by the GdkPaintable interface */
-static void
-gtk_nuclear_icon_snapshot (GdkPaintable *paintable,
-                           GdkSnapshot  *snapshot,
-                           double        width,
-                           double        height)
+/* This is the function that draws the actual icon.
+ * We make it a custom function and define it in the paintable.h header
+ * so that it can be called from all the other demos, too.
+ */
+void
+gtk_nuclear_snapshot (GtkSnapshot *snapshot,
+                      double       width,
+                      double       height,
+                      double       rotation)
 {
 #define RADIUS 0.3
   cairo_t *cr;
   double size;
 
-  /* The snapshot function is the only function we need to implement.
-   * It does the actual drawing of the paintable.
-   */
-
   gtk_snapshot_append_color (snapshot,
                              &(GdkRGBA) { 0.9, 0.75, 0.15, 1.0 },
                              &GRAPHENE_RECT_INIT (0, 0, width, height),
@@ -64,6 +67,7 @@ gtk_nuclear_icon_snapshot (GdkPaintable *paintable,
                                   "Radioactive Icon");
   cairo_translate (cr, width / 2.0, height / 2.0);
   cairo_scale (cr, size, size);
+  cairo_rotate (cr, rotation);
 
   cairo_arc (cr, 0, 0, 0.1, - G_PI, G_PI);
   cairo_fill (cr);
@@ -76,6 +80,24 @@ gtk_nuclear_icon_snapshot (GdkPaintable *paintable,
   cairo_destroy (cr);
 }
 
+/* Here, we implement the functionality required by the GdkPaintable interface */
+static void
+gtk_nuclear_icon_snapshot (GdkPaintable *paintable,
+                           GdkSnapshot  *snapshot,
+                           double        width,
+                           double        height)
+{
+  GtkNuclearIcon *nuclear = GTK_NUCLEAR_ICON (paintable);
+
+  /* The snapshot function is the only function we need to implement.
+   * It does the actual drawing of the paintable.
+   */
+
+  gtk_nuclear_snapshot (snapshot,
+                        width, height,
+                        nuclear->rotation);
+}
+
 static GdkPaintableFlags
 gtk_nuclear_icon_get_flags (GdkPaintable *paintable)
 {
@@ -113,11 +135,19 @@ gtk_nuclear_icon_init (GtkNuclearIcon *nuclear)
 {
 }
 
-/* And finally, we add the simple constructor we declared in the header. */
+/* And finally, we add a simple constructor.
+ * It is declared in the header so that the other examples
+ * can use it.
+ */
 GdkPaintable *
-gtk_nuclear_icon_new (void)
+gtk_nuclear_icon_new (double rotation)
 {
-  return g_object_new (GTK_TYPE_NUCLEAR_ICON, NULL);
+  GtkNuclearIcon *nuclear;
+
+  nuclear = g_object_new (GTK_TYPE_NUCLEAR_ICON, NULL);
+  nuclear->rotation = rotation;
+
+  return GDK_PAINTABLE (nuclear);
 }
 
 GtkWidget *
@@ -134,7 +164,7 @@ do_paintable (GtkWidget *do_widget)
       gtk_window_set_title (GTK_WINDOW (window), "Nuclear Icon");
       gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
 
-      nuclear = gtk_nuclear_icon_new ();
+      nuclear = gtk_nuclear_icon_new (0.0);
       image = gtk_image_new_from_paintable (nuclear);
       gtk_container_add (GTK_CONTAINER (window), image);
       g_object_unref (nuclear);
diff --git a/demos/gtk-demo/paintable.h b/demos/gtk-demo/paintable.h
new file mode 100644 (file)
index 0000000..0f561b2
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef __PAINTABLE_H__
+#define __PAINTABLE_H__
+
+#include <gtk/gtk.h>
+
+void            gtk_nuclear_snapshot           (GtkSnapshot     *snapshot,
+                                                double           width,
+                                                double           height,
+                                                double           rotation);
+
+GdkPaintable *  gtk_nuclear_icon_new            (double          rotation);
+GdkPaintable *  gtk_nuclear_animation_new       (void);
+
+#endif /* __PAINTABLE_H__ */
diff --git a/demos/gtk-demo/paintable_animated.c b/demos/gtk-demo/paintable_animated.c
new file mode 100644 (file)
index 0000000..b606764
--- /dev/null
@@ -0,0 +1,209 @@
+/* Paintable/An animated paintable
+ *
+ * GdkPaintable also allows paintables to change.
+ *
+ * This demo code gives an example of how this could work. It builds
+ * on the previous simple example.
+ *
+ * Paintables can also change their size, this works similarly, but
+ * we will not demonstrate this here as our icon does not have any size.
+ */
+
+#include <gtk/gtk.h>
+
+#include "paintable.h"
+
+static GtkWidget *window = NULL;
+
+/* First, add the boilerplate for the object itself.
+ * This part would normally go in the header.
+ */
+#define GTK_TYPE_NUCLEAR_ANIMATION (gtk_nuclear_animation_get_type ())
+G_DECLARE_FINAL_TYPE (GtkNuclearAnimation, gtk_nuclear_animation, GTK, NUCLEAR_ANIMATION, GObject)
+
+/* Do a full rotation in 5 seconds.
+ * We will register the timeout for doing a single step to
+ * be executed every 10ms, which means after 1000 steps
+ * 10s will have elapsed.
+ */
+#define MAX_PROGRESS 500
+
+/* Declare the struct. */
+struct _GtkNuclearAnimation
+{
+  GObject parent_instance;
+
+  /* This variable stores the progress of our animation.
+   * We just count upwards until we hit MAX_PROGRESS and
+   * then start from scratch.
+   */
+  int progress;
+
+  /* This variable holds the ID of the timer that updates
+   * our progress variable.
+   * We need to keep track of it so that we can remove it
+   * again.
+   */
+  guint source_id;
+};
+
+struct _GtkNuclearAnimationClass
+{
+  GObjectClass parent_class;
+};
+
+/* Again, we implement the functionality required by the GdkPaintable interface */
+static void
+gtk_nuclear_animation_snapshot (GdkPaintable *paintable,
+                                GdkSnapshot  *snapshot,
+                                double        width,
+                                double        height)
+{
+  GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (paintable);
+
+  /* We call the function from the previous example here. */
+  gtk_nuclear_snapshot (snapshot,
+                        width, height,
+                        2 * G_PI * nuclear->progress / MAX_PROGRESS);
+}
+
+static GdkPaintable *
+gtk_nuclear_animation_get_current_image (GdkPaintable *paintable)
+{
+  GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (paintable);
+
+  /* For non-static paintables, this function needs to be implemented.
+   * It must return a static paintable with the same contents
+   * as this one currently has.
+   *
+   * Luckily we added the rotation property to the nuclear icon
+   * object previously, so we can just return an instance of that one.
+   */
+  return gtk_nuclear_icon_new (2 * G_PI * nuclear->progress / MAX_PROGRESS);
+}
+
+static GdkPaintableFlags
+gtk_nuclear_animation_get_flags (GdkPaintable *paintable)
+{
+  /* This time, we cannot set the static contents flag because our animation
+   * changes the contents.
+   * However, our size still doesn't change, so report that flag.
+   */
+  return GDK_PAINTABLE_STATIC_SIZE;
+}
+
+static void
+gtk_nuclear_animation_paintable_init (GdkPaintableInterface *iface)
+{
+  iface->snapshot = gtk_nuclear_animation_snapshot;
+  iface->get_current_image = gtk_nuclear_animation_get_current_image;
+  iface->get_flags = gtk_nuclear_animation_get_flags;
+}
+
+/* When defining the GType, we need to implement the GdkPaintable interface */
+G_DEFINE_TYPE_WITH_CODE (GtkNuclearAnimation, gtk_nuclear_animation, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
+                                                gtk_nuclear_animation_paintable_init))
+
+/* This time, we need to implement the finalize function,
+ */
+static void
+gtk_nuclear_animation_finalize (GObject *object)
+{
+  GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (object);
+
+  /* Remove the timeout we registered when constructing
+   * the object. 
+   */
+  g_source_remove (nuclear->source_id);
+
+  /* Don't forget to chain up to the parent class' implementation
+   * of the finalize function.
+   */
+  G_OBJECT_CLASS (gtk_nuclear_animation_parent_class)->finalize (object);
+}
+
+/* In the class declaration, we need to add our finalize function.
+ */
+static void
+gtk_nuclear_animation_class_init (GtkNuclearAnimationClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize = gtk_nuclear_animation_finalize;
+}
+
+static gboolean
+gtk_nuclear_animation_step (gpointer data)
+{
+  GtkNuclearAnimation *nuclear = data;
+
+  /* Add 1 to the progress and reset it when we've reached
+   * the maximum value.
+   * The animation will rotate by 360 degrees at MAX_PROGRESS
+   * so it will be identical to the original unrotated one.
+   */
+  nuclear->progress = (nuclear->progress + 1) % MAX_PROGRESS;
+
+  /* Now we need to tell all listeners that we've changed out contents
+   * so that they can redraw this paintable.
+   */
+  gdk_paintable_invalidate_contents (GDK_PAINTABLE (nuclear));
+
+  /* We want this timeout function to be called repeatedly,
+   * so we return this value here.
+   * If this was a single-shot timeout, we could also
+   * return G_SOURCE_REMOVE here to get rid of it.
+   */
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+gtk_nuclear_animation_init (GtkNuclearAnimation *nuclear)
+{
+  /* Add a timer here that constantly updates our animations.
+   * We want to update it often enough to guarantee a smooth animation.
+   *
+   * Ideally, we'd attach to the frame clock, but because we do
+   * not have it available here, we just use a regular timeout
+   * that hopefully triggers often enough to be smooth.
+   */
+  nuclear->source_id = g_timeout_add (10,
+                                      gtk_nuclear_animation_step,
+                                      nuclear);
+}
+
+/* And finally, we add the simple constructor we declared in the header. */
+GdkPaintable *
+gtk_nuclear_animation_new (void)
+{
+  return g_object_new (GTK_TYPE_NUCLEAR_ANIMATION, NULL);
+}
+
+GtkWidget *
+do_paintable_animated (GtkWidget *do_widget)
+{
+  GdkPaintable *nuclear;
+  GtkWidget *image;
+
+  if (!window)
+    {
+      window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+      gtk_window_set_display (GTK_WINDOW (window),
+                              gtk_widget_get_display (do_widget));
+      gtk_window_set_title (GTK_WINDOW (window), "Nuclear Animation");
+      gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
+
+      nuclear = gtk_nuclear_animation_new ();
+      image = gtk_image_new_from_paintable (nuclear);
+      gtk_container_add (GTK_CONTAINER (window), image);
+      g_object_unref (nuclear);
+    }
+
+  if (!gtk_widget_get_visible (window))
+    gtk_widget_show (window);
+  else
+    gtk_widget_destroy (window);
+
+  return window;
+}