1 | /* Paintable/Animated Paintable |
2 | * |
3 | * GdkPaintable also allows paintables to change. |
4 | * |
5 | * This demo code gives an example of how this could work. It builds |
6 | * on the previous simple example. |
7 | * |
8 | * Paintables can also change their size, this works similarly, but |
9 | * we will not demonstrate this here as our icon does not have any size. |
10 | */ |
11 | |
12 | #include <gtk/gtk.h> |
13 | |
14 | #include "paintable.h" |
15 | |
16 | static GtkWidget *window = NULL; |
17 | |
18 | /* First, add the boilerplate for the object itself. |
19 | * This part would normally go in the header. |
20 | */ |
21 | #define GTK_TYPE_NUCLEAR_ANIMATION (gtk_nuclear_animation_get_type ()) |
22 | G_DECLARE_FINAL_TYPE (GtkNuclearAnimation, gtk_nuclear_animation, GTK, NUCLEAR_ANIMATION, GObject) |
23 | |
24 | /* Do a full rotation in 5 seconds. |
25 | * We will register the timeout for doing a single step to |
26 | * be executed every 10ms, which means after 1000 steps |
27 | * 10s will have elapsed. |
28 | */ |
29 | #define MAX_PROGRESS 500 |
30 | |
31 | /* Declare the struct. */ |
32 | struct _GtkNuclearAnimation |
33 | { |
34 | GObject parent_instance; |
35 | |
36 | gboolean draw_background; |
37 | |
38 | /* This variable stores the progress of our animation. |
39 | * We just count upwards until we hit MAX_PROGRESS and |
40 | * then start from scratch. |
41 | */ |
42 | int progress; |
43 | |
44 | /* This variable holds the ID of the timer that updates |
45 | * our progress variable. |
46 | * We need to keep track of it so that we can remove it |
47 | * again. |
48 | */ |
49 | guint source_id; |
50 | }; |
51 | |
52 | struct _GtkNuclearAnimationClass |
53 | { |
54 | GObjectClass parent_class; |
55 | }; |
56 | |
57 | /* Again, we implement the functionality required by the GdkPaintable interface */ |
58 | static void |
59 | gtk_nuclear_animation_snapshot (GdkPaintable *paintable, |
60 | GdkSnapshot *snapshot, |
61 | double width, |
62 | double height) |
63 | { |
64 | GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (ptr: paintable); |
65 | |
66 | /* We call the function from the previous example here. */ |
67 | gtk_nuclear_snapshot (snapshot, |
68 | foreground: &(GdkRGBA) { 0, 0, 0, 1 }, /* black */ |
69 | background: nuclear->draw_background |
70 | ? &(GdkRGBA) { 0.9, 0.75, 0.15, 1.0 } /* yellow */ |
71 | : &(GdkRGBA) { 0, 0, 0, 0 }, /* transparent */ |
72 | width, height, |
73 | rotation: 2 * G_PI * nuclear->progress / MAX_PROGRESS); |
74 | } |
75 | |
76 | static GdkPaintable * |
77 | gtk_nuclear_animation_get_current_image (GdkPaintable *paintable) |
78 | { |
79 | GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (ptr: paintable); |
80 | |
81 | /* For non-static paintables, this function needs to be implemented. |
82 | * It must return a static paintable with the same contents |
83 | * as this one currently has. |
84 | * |
85 | * Luckily we added the rotation property to the nuclear icon |
86 | * object previously, so we can just return an instance of that one. |
87 | */ |
88 | return gtk_nuclear_icon_new (rotation: 2 * G_PI * nuclear->progress / MAX_PROGRESS); |
89 | } |
90 | |
91 | static GdkPaintableFlags |
92 | gtk_nuclear_animation_get_flags (GdkPaintable *paintable) |
93 | { |
94 | /* This time, we cannot set the static contents flag because our animation |
95 | * changes the contents. |
96 | * However, our size still doesn't change, so report that flag. |
97 | */ |
98 | return GDK_PAINTABLE_STATIC_SIZE; |
99 | } |
100 | |
101 | static void |
102 | gtk_nuclear_animation_paintable_init (GdkPaintableInterface *iface) |
103 | { |
104 | iface->snapshot = gtk_nuclear_animation_snapshot; |
105 | iface->get_current_image = gtk_nuclear_animation_get_current_image; |
106 | iface->get_flags = gtk_nuclear_animation_get_flags; |
107 | } |
108 | |
109 | /* When defining the GType, we need to implement the GdkPaintable interface */ |
110 | G_DEFINE_TYPE_WITH_CODE (GtkNuclearAnimation, gtk_nuclear_animation, G_TYPE_OBJECT, |
111 | G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, |
112 | gtk_nuclear_animation_paintable_init)) |
113 | |
114 | /* This time, we need to implement the finalize function, |
115 | */ |
116 | static void |
117 | gtk_nuclear_animation_finalize (GObject *object) |
118 | { |
119 | GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (ptr: object); |
120 | |
121 | /* Remove the timeout we registered when constructing |
122 | * the object. |
123 | */ |
124 | g_source_remove (tag: nuclear->source_id); |
125 | |
126 | /* Don't forget to chain up to the parent class' implementation |
127 | * of the finalize function. |
128 | */ |
129 | G_OBJECT_CLASS (gtk_nuclear_animation_parent_class)->finalize (object); |
130 | } |
131 | |
132 | /* In the class declaration, we need to add our finalize function. |
133 | */ |
134 | static void |
135 | gtk_nuclear_animation_class_init (GtkNuclearAnimationClass *klass) |
136 | { |
137 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
138 | |
139 | gobject_class->finalize = gtk_nuclear_animation_finalize; |
140 | } |
141 | |
142 | static gboolean |
143 | gtk_nuclear_animation_step (gpointer data) |
144 | { |
145 | GtkNuclearAnimation *nuclear = data; |
146 | |
147 | /* Add 1 to the progress and reset it when we've reached |
148 | * the maximum value. |
149 | * The animation will rotate by 360 degrees at MAX_PROGRESS |
150 | * so it will be identical to the original unrotated one. |
151 | */ |
152 | nuclear->progress = (nuclear->progress + 1) % MAX_PROGRESS; |
153 | |
154 | /* Now we need to tell all listeners that we've changed out contents |
155 | * so that they can redraw this paintable. |
156 | */ |
157 | gdk_paintable_invalidate_contents (paintable: GDK_PAINTABLE (ptr: nuclear)); |
158 | |
159 | /* We want this timeout function to be called repeatedly, |
160 | * so we return this value here. |
161 | * If this was a single-shot timeout, we could also |
162 | * return G_SOURCE_REMOVE here to get rid of it. |
163 | */ |
164 | return G_SOURCE_CONTINUE; |
165 | } |
166 | |
167 | static void |
168 | gtk_nuclear_animation_init (GtkNuclearAnimation *nuclear) |
169 | { |
170 | /* Add a timer here that constantly updates our animations. |
171 | * We want to update it often enough to guarantee a smooth animation. |
172 | * |
173 | * Ideally, we'd attach to the frame clock, but because we do |
174 | * not have it available here, we just use a regular timeout |
175 | * that hopefully triggers often enough to be smooth. |
176 | */ |
177 | nuclear->source_id = g_timeout_add (interval: 10, |
178 | function: gtk_nuclear_animation_step, |
179 | data: nuclear); |
180 | } |
181 | |
182 | /* And finally, we add the simple constructor we declared in the header. */ |
183 | GdkPaintable * |
184 | gtk_nuclear_animation_new (gboolean draw_background) |
185 | { |
186 | GtkNuclearAnimation *nuclear; |
187 | |
188 | nuclear = g_object_new (GTK_TYPE_NUCLEAR_ANIMATION, NULL); |
189 | |
190 | nuclear->draw_background = draw_background; |
191 | |
192 | return GDK_PAINTABLE (ptr: nuclear); |
193 | } |
194 | |
195 | GtkWidget * |
196 | do_paintable_animated (GtkWidget *do_widget) |
197 | { |
198 | GdkPaintable *nuclear; |
199 | GtkWidget *image; |
200 | |
201 | if (!window) |
202 | { |
203 | window = gtk_window_new (); |
204 | gtk_window_set_display (GTK_WINDOW (window), |
205 | display: gtk_widget_get_display (widget: do_widget)); |
206 | gtk_window_set_title (GTK_WINDOW (window), title: "Nuclear Animation" ); |
207 | gtk_window_set_default_size (GTK_WINDOW (window), width: 300, height: 200); |
208 | g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer *)&window); |
209 | |
210 | nuclear = gtk_nuclear_animation_new (TRUE); |
211 | image = gtk_image_new_from_paintable (paintable: nuclear); |
212 | gtk_window_set_child (GTK_WINDOW (window), child: image); |
213 | g_object_unref (object: nuclear); |
214 | } |
215 | |
216 | if (!gtk_widget_get_visible (widget: window)) |
217 | gtk_widget_show (widget: window); |
218 | else |
219 | gtk_window_destroy (GTK_WINDOW (window)); |
220 | |
221 | return window; |
222 | } |
223 | |