/* 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;
}
