summaryrefslogtreecommitdiff
path: root/demos/gtk-demo/paintable_animated.c
blob: b606764ca5830cf0805918618885f0ba5708fa2d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
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;
}