5bd977dd3c
Creating an FBO may be expensive, so we should avoid the operation if possible. When transitioning between theme nodes, the widget's opacity is used to paint to the offscreen textures which are blended together - this means that the textures have to be recreated each time the widget's opacity changes. It is much more effective to paint the textures at full opacity and respect the widget's paint opacity when blending the textures together. https://bugzilla.gnome.org/show_bug.cgi?id=627085
439 lines
14 KiB
C
439 lines
14 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
/* Theme node transitions for StWidget.
|
|
*
|
|
* Copyright (C) 2010 Florian Müllner <fmuellner@gnome.org>
|
|
*
|
|
* 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;
|
|
|
|
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 if the
|
|
* transition is in an intermediate state, 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))
|
|
{
|
|
if (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);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (clutter_timeline_get_elapsed_time (priv->timeline) > 0)
|
|
{
|
|
clutter_timeline_stop (priv->timeline);
|
|
g_signal_emit (transition, signals[COMPLETED], 0);
|
|
}
|
|
else
|
|
{
|
|
guint new_duration = st_theme_node_get_transition_duration (new_node);
|
|
|
|
clutter_timeline_set_duration (priv->timeline, new_duration);
|
|
g_object_unref (priv->new_theme_node);
|
|
priv->new_theme_node = g_object_ref (new_node);
|
|
priv->needs_setup = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 gboolean
|
|
setup_framebuffers (StThemeNodeTransition *transition,
|
|
const ClutterActorBox *allocation)
|
|
{
|
|
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;
|
|
|
|
g_return_val_if_fail (width > 0, FALSE);
|
|
g_return_val_if_fail (height > 0, FALSE);
|
|
|
|
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);
|
|
|
|
g_return_val_if_fail (priv->old_texture != COGL_INVALID_HANDLE, FALSE);
|
|
g_return_val_if_fail (priv->new_texture != COGL_INVALID_HANDLE, FALSE);
|
|
|
|
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);
|
|
|
|
g_return_val_if_fail (priv->old_offscreen != COGL_INVALID_HANDLE, FALSE);
|
|
g_return_val_if_fail (priv->new_offscreen != COGL_INVALID_HANDLE, FALSE);
|
|
|
|
if (priv->material)
|
|
cogl_handle_unref (priv->material);
|
|
priv->material = cogl_material_new ();
|
|
|
|
cogl_material_set_layer_combine (priv->material, 0,
|
|
"RGBA = REPLACE (TEXTURE)",
|
|
NULL);
|
|
cogl_material_set_layer_combine (priv->material, 1,
|
|
"RGBA = INTERPOLATE (PREVIOUS, "
|
|
"TEXTURE, "
|
|
"CONSTANT[A])",
|
|
NULL);
|
|
cogl_material_set_layer_combine (priv->material, 2,
|
|
"RGBA = MODULATE (PREVIOUS, PRIMARY)",
|
|
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, 255);
|
|
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, 255);
|
|
cogl_pop_framebuffer ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
st_theme_node_transition_paint (StThemeNodeTransition *transition,
|
|
ClutterActorBox *allocation,
|
|
guint8 paint_opacity)
|
|
{
|
|
StThemeNodeTransitionPrivate *priv = transition->priv;
|
|
|
|
CoglColor constant;
|
|
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))
|
|
priv->needs_setup = TRUE;
|
|
|
|
if (priv->needs_setup)
|
|
{
|
|
priv->last_allocation = *allocation;
|
|
|
|
calculate_offscreen_box (transition, allocation);
|
|
priv->needs_setup = !setup_framebuffers (transition, allocation);
|
|
}
|
|
|
|
cogl_color_set_from_4f (&constant, 0., 0., 0.,
|
|
clutter_alpha_get_alpha (priv->alpha));
|
|
cogl_material_set_layer_combine_constant (priv->material, 1, &constant);
|
|
|
|
cogl_material_set_color4ub (priv->material,
|
|
paint_opacity, paint_opacity,
|
|
paint_opacity, paint_opacity);
|
|
|
|
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);
|
|
}
|