1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * st-theme-node-transition.c: Theme node transitions for StWidget.
4 *
5 * Copyright 2010 Florian Mç«Żllner
6 * Copyright 2010 Adel Gadllah
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as
10 * published by the Free Software Foundation, either version 2.1 of
11 * the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope it will be useful, but WITHOUT ANY
14 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
16 * more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "st-theme-node-transition.h"
23
24 enum {
25 COMPLETED,
26 NEW_FRAME,
27 LAST_SIGNAL
28 };
29
30 #define ST_THEME_NODE_TRANSITION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_THEME_NODE_TRANSITION, StThemeNodeTransitionPrivate))
31
32 struct _StThemeNodeTransitionPrivate {
33 StThemeNode *old_theme_node;
34 StThemeNode *new_theme_node;
35
36 CoglHandle old_texture;
37 CoglHandle new_texture;
38
39 CoglHandle old_offscreen;
40 CoglHandle new_offscreen;
41
42 CoglHandle material;
43
44 ClutterAlpha *alpha;
45 ClutterTimeline *timeline;
46
47 guint timeline_completed_id;
48 guint timeline_new_frame_id;
49
50 ClutterActorBox last_allocation;
51 ClutterActorBox offscreen_box;
52
53 gboolean needs_setup;
54 };
55
56 static guint signals[LAST_SIGNAL] = { 0 };
57
58 G_DEFINE_TYPE (StThemeNodeTransition, st_theme_node_transition, G_TYPE_OBJECT);
59
60
61 static void
62 on_timeline_completed (ClutterTimeline *timeline,
63 StThemeNodeTransition *transition)
64 {
65 g_signal_emit (transition, signals[COMPLETED], 0);
66 }
67
68 static void
69 on_timeline_new_frame (ClutterTimeline *timeline,
70 gint frame_num,
71 StThemeNodeTransition *transition)
72 {
73 g_signal_emit (transition, signals[NEW_FRAME], 0);
74 }
75
76 StThemeNodeTransition *
77 st_theme_node_transition_new (StThemeNode *from_node,
78 StThemeNode *to_node,
79 guint duration)
80 {
81 StThemeNodeTransition *transition;
82
83 g_return_val_if_fail (ST_IS_THEME_NODE (from_node), NULL);
84 g_return_val_if_fail (ST_IS_THEME_NODE (to_node), NULL);
85
86 duration = st_theme_node_get_transition_duration (to_node);
87
88 transition = g_object_new (ST_TYPE_THEME_NODE_TRANSITION,
89 NULL);
90
91 transition->priv->old_theme_node = g_object_ref (from_node);
92 transition->priv->new_theme_node = g_object_ref (to_node);
93
94 transition->priv->alpha = clutter_alpha_new ();
'clutter_alpha_new' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-alpha.h:105)
(emitted by gcc)
95 transition->priv->timeline = clutter_timeline_new (duration);
96
97 transition->priv->timeline_completed_id =
98 g_signal_connect (transition->priv->timeline, "completed",
99 G_CALLBACK (on_timeline_completed), transition);
100 transition->priv->timeline_new_frame_id =
101 g_signal_connect (transition->priv->timeline, "new-frame",
102 G_CALLBACK (on_timeline_new_frame), transition);
103
104 clutter_alpha_set_mode (transition->priv->alpha, CLUTTER_EASE_IN_OUT_QUAD);
'clutter_alpha_set_mode' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-alpha.h:131)
(emitted by gcc)
105 clutter_alpha_set_timeline (transition->priv->alpha,
'clutter_alpha_set_timeline' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-alpha.h:126)
(emitted by gcc)
106 transition->priv->timeline);
107
108 clutter_timeline_start (transition->priv->timeline);
109
110 return transition;
111 }
112
113 void
114 st_theme_node_transition_update (StThemeNodeTransition *transition,
115 StThemeNode *new_node)
116 {
117 StThemeNodeTransitionPrivate *priv = transition->priv;
118 StThemeNode *old_node;
119 ClutterTimelineDirection direction;
120
121 g_return_if_fail (ST_IS_THEME_NODE_TRANSITION (transition));
122 g_return_if_fail (ST_IS_THEME_NODE (new_node));
123
124 direction = clutter_timeline_get_direction (priv->timeline);
125 old_node = (direction == CLUTTER_TIMELINE_FORWARD) ? priv->old_theme_node
126 : priv->new_theme_node;
127
128 /* If the update is the reversal of the current transition,
129 * we reverse the timeline.
130 * Otherwise, we should initiate a new transition from the
131 * current state to the new one; this is hard to do if the
132 * transition is in an intermediate state, so we just cancel
133 * the ongoing transition in that case.
134 * Note that reversing a timeline before any time elapsed
135 * results in the timeline's time position being set to the
136 * full duration - this is not what we want, so we cancel the
137 * transition as well in that case.
138 */
139 if (st_theme_node_equal (new_node, old_node))
140 {
141 if (clutter_timeline_get_elapsed_time (priv->timeline) > 0)
142 {
143 if (direction == CLUTTER_TIMELINE_FORWARD)
144 clutter_timeline_set_direction (priv->timeline,
145 CLUTTER_TIMELINE_BACKWARD);
146 else
147 clutter_timeline_set_direction (priv->timeline,
148 CLUTTER_TIMELINE_FORWARD);
149 }
150 else
151 {
152 clutter_timeline_stop (priv->timeline);
153 g_signal_emit (transition, signals[COMPLETED], 0);
154 }
155 }
156 else
157 {
158 if (clutter_timeline_get_elapsed_time (priv->timeline) > 0)
159 {
160 clutter_timeline_stop (priv->timeline);
161 g_signal_emit (transition, signals[COMPLETED], 0);
162 }
163 else
164 {
165 guint new_duration = st_theme_node_get_transition_duration (new_node);
166
167 clutter_timeline_set_duration (priv->timeline, new_duration);
168
169 /* If the change doesn't affect painting, we don't need to redraw,
170 * but we still need to replace the node so that we properly share
171 * caching with the painting that happens after the transition finishes.
172 */
173 if (!st_theme_node_paint_equal (priv->new_theme_node, new_node))
174 priv->needs_setup = TRUE;
175
176 g_object_unref (priv->new_theme_node);
177 priv->new_theme_node = g_object_ref (new_node);
178 }
179 }
180 }
181
182 static void
183 calculate_offscreen_box (StThemeNodeTransition *transition,
184 const ClutterActorBox *allocation)
185 {
186 ClutterActorBox paint_box;
187
188 st_theme_node_transition_get_paint_box (transition,
189 allocation,
190 &paint_box);
191 transition->priv->offscreen_box.x1 = paint_box.x1 - allocation->x1;
192 transition->priv->offscreen_box.y1 = paint_box.y1 - allocation->y1;
193 transition->priv->offscreen_box.x2 = paint_box.x2 - allocation->x1;
194 transition->priv->offscreen_box.y2 = paint_box.y2 - allocation->y1;
195 }
196
197 void
198 st_theme_node_transition_get_paint_box (StThemeNodeTransition *transition,
199 const ClutterActorBox *allocation,
200 ClutterActorBox *paint_box)
201 {
202 StThemeNodeTransitionPrivate *priv = transition->priv;
203 ClutterActorBox old_node_box, new_node_box;
204
205 st_theme_node_get_paint_box (priv->old_theme_node,
206 allocation,
207 &old_node_box);
208
209 st_theme_node_get_paint_box (priv->new_theme_node,
210 allocation,
211 &new_node_box);
212
213 paint_box->x1 = MIN (old_node_box.x1, new_node_box.x1);
214 paint_box->y1 = MIN (old_node_box.y1, new_node_box.y1);
215 paint_box->x2 = MAX (old_node_box.x2, new_node_box.x2);
216 paint_box->y2 = MAX (old_node_box.y2, new_node_box.y2);
217 }
218
219 static gboolean
220 setup_framebuffers (StThemeNodeTransition *transition,
221 const ClutterActorBox *allocation)
222 {
223 StThemeNodeTransitionPrivate *priv = transition->priv;
224 CoglColor clear_color = { 0, 0, 0, 0 };
225 guint width, height;
226
227 /* template material to avoid unnecessary shader compilation */
228 static CoglHandle material_template = COGL_INVALID_HANDLE;
229
230 width = priv->offscreen_box.x2 - priv->offscreen_box.x1;
231 height = priv->offscreen_box.y2 - priv->offscreen_box.y1;
232
233 g_return_val_if_fail (width > 0, FALSE);
234 g_return_val_if_fail (height > 0, FALSE);
235
236 if (priv->old_texture)
237 cogl_handle_unref (priv->old_texture);
238 priv->old_texture = cogl_texture_new_with_size (width, height,
239 COGL_TEXTURE_NO_SLICING,
240 COGL_PIXEL_FORMAT_ANY);
241
242 if (priv->new_texture)
243 cogl_handle_unref (priv->new_texture);
244 priv->new_texture = cogl_texture_new_with_size (width, height,
245 COGL_TEXTURE_NO_SLICING,
246 COGL_PIXEL_FORMAT_ANY);
247
248 g_return_val_if_fail (priv->old_texture != COGL_INVALID_HANDLE, FALSE);
249 g_return_val_if_fail (priv->new_texture != COGL_INVALID_HANDLE, FALSE);
250
251 if (priv->old_offscreen)
252 cogl_handle_unref (priv->old_offscreen);
253 priv->old_offscreen = cogl_offscreen_new_to_texture (priv->old_texture);
254
255 if (priv->new_offscreen)
256 cogl_handle_unref (priv->new_offscreen);
257 priv->new_offscreen = cogl_offscreen_new_to_texture (priv->new_texture);
258
259 g_return_val_if_fail (priv->old_offscreen != COGL_INVALID_HANDLE, FALSE);
260 g_return_val_if_fail (priv->new_offscreen != COGL_INVALID_HANDLE, FALSE);
261
262 if (priv->material == NULL)
263 {
264 if (G_UNLIKELY (material_template == COGL_INVALID_HANDLE))
265 {
266 material_template = cogl_material_new ();
267
268 cogl_material_set_layer_combine (material_template, 0,
269 "RGBA = REPLACE (TEXTURE)",
270 NULL);
271 cogl_material_set_layer_combine (material_template, 1,
272 "RGBA = INTERPOLATE (PREVIOUS, "
273 "TEXTURE, "
274 "CONSTANT[A])",
275 NULL);
276 cogl_material_set_layer_combine (material_template, 2,
277 "RGBA = MODULATE (PREVIOUS, "
278 "PRIMARY)",
279 NULL);
280 }
281 priv->material = cogl_material_copy (material_template);
282 }
283
284 cogl_material_set_layer (priv->material, 0, priv->new_texture);
285 cogl_material_set_layer (priv->material, 1, priv->old_texture);
286
287 cogl_push_framebuffer (priv->old_offscreen);
288 cogl_clear (&clear_color, COGL_BUFFER_BIT_COLOR);
289 cogl_ortho (priv->offscreen_box.x1, priv->offscreen_box.x2,
290 priv->offscreen_box.y2, priv->offscreen_box.y1,
291 0.0, 1.0);
292 st_theme_node_paint (priv->old_theme_node, allocation, 255);
293 cogl_pop_framebuffer ();
294
295 cogl_push_framebuffer (priv->new_offscreen);
296 cogl_clear (&clear_color, COGL_BUFFER_BIT_COLOR);
297 cogl_ortho (priv->offscreen_box.x1, priv->offscreen_box.x2,
298 priv->offscreen_box.y2, priv->offscreen_box.y1,
299 0.0, 1.0);
300 st_theme_node_paint (priv->new_theme_node, allocation, 255);
301 cogl_pop_framebuffer ();
302
303 return TRUE;
304 }
305
306 void
307 st_theme_node_transition_paint (StThemeNodeTransition *transition,
308 ClutterActorBox *allocation,
309 guint8 paint_opacity)
310 {
311 StThemeNodeTransitionPrivate *priv = transition->priv;
312
313 CoglColor constant;
314 float tex_coords[] = {
315 0.0, 0.0, 1.0, 1.0,
316 0.0, 0.0, 1.0, 1.0,
317 };
318
319 g_return_if_fail (ST_IS_THEME_NODE (priv->old_theme_node));
320 g_return_if_fail (ST_IS_THEME_NODE (priv->new_theme_node));
321
322 if (!clutter_actor_box_equal (allocation, &priv->last_allocation))
323 priv->needs_setup = TRUE;
324
325 if (priv->needs_setup)
326 {
327 priv->last_allocation = *allocation;
328
329 calculate_offscreen_box (transition, allocation);
330 priv->needs_setup = !setup_framebuffers (transition, allocation);
331
332 if (priv->needs_setup) /* setting up framebuffers failed */
333 return;
334 }
335
336 cogl_color_set_from_4f (&constant, 0., 0., 0.,
337 clutter_alpha_get_alpha (priv->alpha));
'clutter_alpha_get_alpha' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-alpha.h:116)
(emitted by gcc)
338 cogl_material_set_layer_combine_constant (priv->material, 1, &constant);
339
340 cogl_material_set_color4ub (priv->material,
341 paint_opacity, paint_opacity,
342 paint_opacity, paint_opacity);
343
344 cogl_set_source (priv->material);
345 cogl_rectangle_with_multitexture_coords (priv->offscreen_box.x1,
346 priv->offscreen_box.y1,
347 priv->offscreen_box.x2,
348 priv->offscreen_box.y2,
349 tex_coords, 8);
350 }
351
352 static void
353 st_theme_node_transition_dispose (GObject *object)
354 {
355 StThemeNodeTransitionPrivate *priv = ST_THEME_NODE_TRANSITION (object)->priv;
356
357 if (priv->old_theme_node)
358 {
359 g_object_unref (priv->old_theme_node);
360 priv->old_theme_node = NULL;
361 }
362
363 if (priv->new_theme_node)
364 {
365 g_object_unref (priv->new_theme_node);
366 priv->new_theme_node = NULL;
367 }
368
369 if (priv->old_texture)
370 {
371 cogl_handle_unref (priv->old_texture);
372 priv->old_texture = NULL;
373 }
374
375 if (priv->new_texture)
376 {
377 cogl_handle_unref (priv->new_texture);
378 priv->new_texture = NULL;
379 }
380
381 if (priv->old_offscreen)
382 {
383 cogl_handle_unref (priv->old_offscreen);
384 priv->old_offscreen = NULL;
385 }
386
387 if (priv->new_offscreen)
388 {
389 cogl_handle_unref (priv->new_offscreen);
390 priv->new_offscreen = NULL;
391 }
392
393 if (priv->material)
394 {
395 cogl_handle_unref (priv->material);
396 priv->material = NULL;
397 }
398
399 if (priv->timeline)
400 {
401 if (priv->timeline_completed_id != 0)
402 g_signal_handler_disconnect (priv->timeline,
403 priv->timeline_completed_id);
404 if (priv->timeline_new_frame_id != 0)
405 g_signal_handler_disconnect (priv->timeline,
406 priv->timeline_new_frame_id);
407
408 g_object_unref (priv->timeline);
409 priv->timeline = NULL;
410 }
411
412 priv->timeline_completed_id = 0;
413 priv->timeline_new_frame_id = 0;
414
415 if (priv->alpha)
416 {
417 g_object_unref (priv->alpha);
418 priv->alpha = NULL;
419 }
420
421 G_OBJECT_CLASS (st_theme_node_transition_parent_class)->dispose (object);
422 }
423
424 static void
425 st_theme_node_transition_init (StThemeNodeTransition *transition)
426 {
427 transition->priv = ST_THEME_NODE_TRANSITION_GET_PRIVATE (transition);
428
429 transition->priv->old_theme_node = NULL;
430 transition->priv->new_theme_node = NULL;
431
432 transition->priv->old_texture = NULL;
433 transition->priv->new_texture = NULL;
434
435 transition->priv->old_offscreen = NULL;
436 transition->priv->new_offscreen = NULL;
437
438 transition->priv->needs_setup = TRUE;
439
440 transition->priv->alpha = NULL;
441 }
442
443 static void
444 st_theme_node_transition_class_init (StThemeNodeTransitionClass *klass)
445 {
446 GObjectClass *object_class = G_OBJECT_CLASS (klass);
447
448 g_type_class_add_private (klass, sizeof (StThemeNodeTransitionPrivate));
449
450 object_class->dispose = st_theme_node_transition_dispose;
451
452 signals[COMPLETED] =
453 g_signal_new ("completed",
454 G_TYPE_FROM_CLASS (klass),
455 G_SIGNAL_RUN_LAST,
456 G_STRUCT_OFFSET (StThemeNodeTransitionClass, completed),
457 NULL, NULL, NULL,
458 G_TYPE_NONE, 0);
459
460 signals[NEW_FRAME] =
461 g_signal_new ("new-frame",
462 G_TYPE_FROM_CLASS (klass),
463 G_SIGNAL_RUN_LAST,
464 G_STRUCT_OFFSET (StThemeNodeTransitionClass, new_frame),
465 NULL, NULL, NULL,
466 G_TYPE_NONE, 0);
467 }