diff --git a/src/Makefile-st.am b/src/Makefile-st.am index b3cc7aa82..5cefa7b19 100644 --- a/src/Makefile-st.am +++ b/src/Makefile-st.am @@ -110,7 +110,8 @@ st_source_private_h = \ st/st-private.h \ st/st-table-private.h \ st/st-theme-private.h \ - st/st-theme-node-private.h + st/st-theme-node-private.h \ + st/st-theme-node-transition.h # please, keep this sorted alphabetically st_source_c = \ @@ -142,6 +143,7 @@ st_source_c = \ st/st-theme-context.c \ st/st-theme-node.c \ st/st-theme-node-drawing.c \ + st/st-theme-node-transition.c \ st/st-tooltip.c \ st/st-widget.c \ $(NULL) diff --git a/src/st/st-theme-node-transition.c b/src/st/st-theme-node-transition.c new file mode 100644 index 000000000..379d04dc2 --- /dev/null +++ b/src/st/st-theme-node-transition.c @@ -0,0 +1,411 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* Theme node transitions for StWidget. + * + * Copyright (C) 2010 Florian Müllner + * + * The St is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The St is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the St; see the file COPYING.LIB. + * If not, write to the Free Software Foundation, Inc., 59 Temple Place - + * Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "st-theme-node-transition.h" + +enum { + COMPLETED, + NEW_FRAME, + LAST_SIGNAL +}; + +#define ST_THEME_NODE_TRANSITION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_THEME_NODE_TRANSITION, StThemeNodeTransitionPrivate)) + +struct _StThemeNodeTransitionPrivate { + StThemeNode *old_theme_node; + StThemeNode *new_theme_node; + + CoglHandle *old_texture; + CoglHandle *new_texture; + + CoglHandle *old_offscreen; + CoglHandle *new_offscreen; + + CoglHandle *material; + + ClutterAlpha *alpha; + ClutterTimeline *timeline; + + guint timeline_completed_id; + guint timeline_new_frame_id; + + ClutterActorBox last_allocation; + ClutterActorBox offscreen_box; + guint8 last_opacity; + + gboolean needs_setup; +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (StThemeNodeTransition, st_theme_node_transition, G_TYPE_OBJECT); + + +static void +on_timeline_completed (ClutterTimeline *timeline, + StThemeNodeTransition *transition) +{ + g_signal_emit (transition, signals[COMPLETED], 0); +} + +static void +on_timeline_new_frame (ClutterTimeline *timeline, + gint frame_num, + StThemeNodeTransition *transition) +{ + g_signal_emit (transition, signals[NEW_FRAME], 0); +} + +StThemeNodeTransition * +st_theme_node_transition_new (StThemeNode *from_node, + StThemeNode *to_node, + guint duration) +{ + StThemeNodeTransition *transition; + + g_return_val_if_fail (ST_IS_THEME_NODE (from_node), NULL); + g_return_val_if_fail (ST_IS_THEME_NODE (to_node), NULL); + + duration = st_theme_node_get_transition_duration (to_node); + + transition = g_object_new (ST_TYPE_THEME_NODE_TRANSITION, + NULL); + + transition->priv->old_theme_node = g_object_ref (from_node); + transition->priv->new_theme_node = g_object_ref (to_node); + + transition->priv->alpha = clutter_alpha_new (); + transition->priv->timeline = clutter_timeline_new (duration); + + transition->priv->timeline_completed_id = + g_signal_connect (transition->priv->timeline, "completed", + G_CALLBACK (on_timeline_completed), transition); + transition->priv->timeline_new_frame_id = + g_signal_connect (transition->priv->timeline, "new-frame", + G_CALLBACK (on_timeline_new_frame), transition); + + clutter_alpha_set_mode (transition->priv->alpha, CLUTTER_EASE_IN_OUT_QUAD); + clutter_alpha_set_timeline (transition->priv->alpha, + transition->priv->timeline); + + clutter_timeline_start (transition->priv->timeline); + + return transition; +} + +void +st_theme_node_transition_update (StThemeNodeTransition *transition, + StThemeNode *new_node) +{ + StThemeNodeTransitionPrivate *priv = transition->priv; + StThemeNode *old_node; + ClutterTimelineDirection direction; + + g_return_if_fail (ST_IS_THEME_NODE_TRANSITION (transition)); + g_return_if_fail (ST_IS_THEME_NODE (new_node)); + + direction = clutter_timeline_get_direction (priv->timeline); + old_node = (direction == CLUTTER_TIMELINE_FORWARD) ? priv->old_theme_node + : priv->new_theme_node; + + /* If the update is the reversal of the current transition, + * we reverse the timeline. + * Otherwise, we should initiate a new transition from the + * current state to the new one; this is hard to do, so we + * just cancel the ongoing transition in that case. + * Note that reversing a timeline before any time elapsed + * results in the timeline's time position being set to the + * full duration - this is not what we want, so we cancel the + * transition as well in that case. + */ + if (st_theme_node_equal (new_node, old_node) + && clutter_timeline_get_elapsed_time (priv->timeline) > 0) + { + if (direction == CLUTTER_TIMELINE_FORWARD) + clutter_timeline_set_direction (priv->timeline, + CLUTTER_TIMELINE_BACKWARD); + else + clutter_timeline_set_direction (priv->timeline, + CLUTTER_TIMELINE_FORWARD); + } + else + { + clutter_timeline_stop (priv->timeline); + g_signal_emit (transition, signals[COMPLETED], 0); + } +} + +static void +calculate_offscreen_box (StThemeNodeTransition *transition, + const ClutterActorBox *allocation) +{ + StThemeNodeTransitionPrivate *priv = transition->priv; + ClutterActorBox old_node_box, new_node_box; + + st_theme_node_get_paint_box (priv->old_theme_node, + allocation, + &old_node_box); + + st_theme_node_get_paint_box (priv->new_theme_node, + allocation, + &new_node_box); + + priv->offscreen_box.x1 = MIN (old_node_box.x1, new_node_box.x1) + - allocation->x1; + priv->offscreen_box.y1 = MIN (old_node_box.y1, new_node_box.y1) + - allocation->y1; + priv->offscreen_box.x2 = MAX (old_node_box.x2, new_node_box.x2) + - allocation->x1; + priv->offscreen_box.y2 = MAX (old_node_box.y2, new_node_box.y2) + - allocation->y1; +} + +static void +setup_framebuffers (StThemeNodeTransition *transition, + const ClutterActorBox *allocation, + guint8 paint_opacity) +{ + StThemeNodeTransitionPrivate *priv = transition->priv; + CoglColor clear_color = { 0, 0, 0, 0 }; + guint width, height; + + width = priv->offscreen_box.x2 - priv->offscreen_box.x1; + height = priv->offscreen_box.y2 - priv->offscreen_box.y1; + + if (priv->old_texture) + cogl_handle_unref (priv->old_texture); + priv->old_texture = cogl_texture_new_with_size (width, height, + COGL_TEXTURE_NO_SLICING, + COGL_PIXEL_FORMAT_ANY); + + if (priv->new_texture) + cogl_handle_unref (priv->new_texture); + priv->new_texture = cogl_texture_new_with_size (width, height, + COGL_TEXTURE_NO_SLICING, + COGL_PIXEL_FORMAT_ANY); + + if (priv->old_offscreen) + cogl_handle_unref (priv->old_offscreen); + priv->old_offscreen = cogl_offscreen_new_to_texture (priv->old_texture); + + if (priv->new_offscreen) + cogl_handle_unref (priv->new_offscreen); + priv->new_offscreen = cogl_offscreen_new_to_texture (priv->new_texture); + + if (priv->material) + cogl_handle_unref (priv->material); + priv->material = cogl_material_new (); + + cogl_material_set_layer_combine (priv->material, 1, + "RGBA = INTERPOLATE (PREVIOUS, " + "TEXTURE, " + "CONSTANT[A])", + NULL); + + cogl_material_set_layer (priv->material, 0, priv->new_texture); + cogl_material_set_layer (priv->material, 1, priv->old_texture); + + cogl_push_framebuffer (priv->old_offscreen); + cogl_clear (&clear_color, COGL_BUFFER_BIT_COLOR); + cogl_ortho (priv->offscreen_box.x1, priv->offscreen_box.x2, + priv->offscreen_box.y2, priv->offscreen_box.y1, + 0.0, 1.0); + st_theme_node_paint (priv->old_theme_node, + allocation, + paint_opacity); + cogl_pop_framebuffer (); + + cogl_push_framebuffer (priv->new_offscreen); + cogl_clear (&clear_color, COGL_BUFFER_BIT_COLOR); + cogl_ortho (priv->offscreen_box.x1, priv->offscreen_box.x2, + priv->offscreen_box.y2, priv->offscreen_box.y1, + 0.0, 1.0); + st_theme_node_paint (priv->new_theme_node, + allocation, + paint_opacity); + cogl_pop_framebuffer (); +} + +void +st_theme_node_transition_paint (StThemeNodeTransition *transition, + ClutterActorBox *allocation, + guint8 paint_opacity) +{ + StThemeNodeTransitionPrivate *priv = transition->priv; + + guint width, height; + CoglColor constant = { 0, 0, 0, 0 }; + float tex_coords[] = { + 0.0, 0.0, 1.0, 1.0, + 0.0, 0.0, 1.0, 1.0, + }; + + g_return_if_fail (ST_IS_THEME_NODE (priv->old_theme_node)); + g_return_if_fail (ST_IS_THEME_NODE (priv->new_theme_node)); + + if (!clutter_actor_box_equal (allocation, &priv->last_allocation) || + paint_opacity != priv->last_opacity) + priv->needs_setup = TRUE; + + if (priv->needs_setup) + { + priv->last_allocation = *allocation; + priv->last_opacity = paint_opacity; + + calculate_offscreen_box (transition, allocation); + setup_framebuffers (transition, allocation, paint_opacity); + + priv->needs_setup = FALSE; + } + + width = cogl_texture_get_width (priv->old_texture); + height = cogl_texture_get_height (priv->old_texture); + + constant.alpha = clutter_alpha_get_alpha (priv->alpha) * paint_opacity; + + cogl_material_set_layer_combine_constant (priv->material, 1, &constant); + + cogl_set_source (priv->material); + cogl_rectangle_with_multitexture_coords (priv->offscreen_box.x1, + priv->offscreen_box.y1, + priv->offscreen_box.x2, + priv->offscreen_box.y2, + tex_coords, 8); +} + +static void +st_theme_node_transition_dispose (GObject *object) +{ + StThemeNodeTransitionPrivate *priv = ST_THEME_NODE_TRANSITION (object)->priv; + + if (priv->old_theme_node) + { + g_object_unref (priv->old_theme_node); + priv->old_theme_node = NULL; + } + + if (priv->new_theme_node) + { + g_object_unref (priv->new_theme_node); + priv->new_theme_node = NULL; + } + + if (priv->old_texture) + { + cogl_handle_unref (priv->old_texture); + priv->old_texture = NULL; + } + + if (priv->new_texture) + { + cogl_handle_unref (priv->new_texture); + priv->new_texture = NULL; + } + + if (priv->old_offscreen) + { + cogl_handle_unref (priv->old_offscreen); + priv->old_offscreen = NULL; + } + + if (priv->new_offscreen) + { + cogl_handle_unref (priv->new_offscreen); + priv->new_offscreen = NULL; + } + + if (priv->material) + { + cogl_handle_unref (priv->material); + priv->material = NULL; + } + + if (priv->timeline) + { + if (priv->timeline_completed_id != 0) + g_signal_handler_disconnect (priv->timeline, + priv->timeline_completed_id); + if (priv->timeline_new_frame_id != 0) + g_signal_handler_disconnect (priv->timeline, + priv->timeline_new_frame_id); + + g_object_unref (priv->timeline); + priv->timeline = NULL; + } + + priv->timeline_completed_id = 0; + priv->timeline_new_frame_id = 0; + + if (priv->alpha) + { + g_object_unref (priv->alpha); + priv->alpha = NULL; + } + + G_OBJECT_CLASS (st_theme_node_transition_parent_class)->dispose (object); +} + +static void +st_theme_node_transition_init (StThemeNodeTransition *transition) +{ + transition->priv = ST_THEME_NODE_TRANSITION_GET_PRIVATE (transition); + + transition->priv->old_theme_node = NULL; + transition->priv->new_theme_node = NULL; + + transition->priv->old_texture = NULL; + transition->priv->new_texture = NULL; + + transition->priv->old_offscreen = NULL; + transition->priv->new_offscreen = NULL; + + transition->priv->needs_setup = TRUE; + + transition->priv->alpha = NULL; +} + +static void +st_theme_node_transition_class_init (StThemeNodeTransitionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (StThemeNodeTransitionPrivate)); + + object_class->dispose = st_theme_node_transition_dispose; + + signals[COMPLETED] = + g_signal_new ("completed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StThemeNodeTransitionClass, completed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[NEW_FRAME] = + g_signal_new ("new-frame", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StThemeNodeTransitionClass, new_frame), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} diff --git a/src/st/st-theme-node-transition.h b/src/st/st-theme-node-transition.h new file mode 100644 index 000000000..2f237b189 --- /dev/null +++ b/src/st/st-theme-node-transition.h @@ -0,0 +1,51 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#ifndef __ST_THEME_NODE_TRANSITION_H__ +#define __ST_THEME_NODE_TRANSITION_H__ + +#include + +#include "st-widget.h" +#include "st-theme-node.h" + +G_BEGIN_DECLS + +#define ST_TYPE_THEME_NODE_TRANSITION (st_theme_node_transition_get_type ()) +#define ST_THEME_NODE_TRANSITION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ST_TYPE_THEME_NODE_TRANSITION, StThemeNodeTransition)) +#define ST_IS_THEME_NODE_TRANSITION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ST_TYPE_THEME_NODE_TRANSITION)) +#define ST_THEME_NODE_TRANSITION_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), ST_TYPE_THEME_NODE_TRANSITION, StThemeNodeTransitionClass)) +#define ST_IS_THEME_NODE_TRANSITION_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), ST_TYPE_THEME_NODE_TRANSITION)) +#define ST_THEME_NODE_TRANSITION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ST_THEME_NODE_TRANSITION, StThemeNodeTransitionClass)) + +typedef struct _StThemeNodeTransition StThemeNodeTransition; +typedef struct _StThemeNodeTransitionClass StThemeNodeTransitionClass; +typedef struct _StThemeNodeTransitionPrivate StThemeNodeTransitionPrivate; + +struct _StThemeNodeTransition { + GObject parent; + + StThemeNodeTransitionPrivate *priv; +}; + +struct _StThemeNodeTransitionClass { + GObjectClass parent_class; + + void (*completed) (StThemeNodeTransition *transition); + void (*new_frame) (StThemeNodeTransition *transition); +}; + +GType st_theme_node_transition_get_type (void) G_GNUC_CONST; + +StThemeNodeTransition *st_theme_node_transition_new (StThemeNode *from_node, + StThemeNode *to_node, + guint duration); + +void st_theme_node_transition_update (StThemeNodeTransition *transition, + StThemeNode *new_node); + +void st_theme_node_transition_paint (StThemeNodeTransition *transition, + ClutterActorBox *allocation, + guint8 paint_opacity); + +G_END_DECLS + +#endif