mirror of
https://github.com/brl/mutter.git
synced 2024-12-24 12:02:04 +00:00
678f99677f
Currently, all timelines install a timeout inside the TimeoutPool they share. Every time the main loop spins, all the timeouts are updated. This, in turn, will usually lead to redraws being queued on the stages. This behaviour leads to the potential starvation of timelines and to excessive redraws. One lesson learned from the games developers is that the scenegraph should be prepared in its entirety before the GL paint sequence is initiated. This means making sure that every ::new-frame signal handler is called before clutter_redraw() is invoked. In order to do so a TimeoutPool is not enough: we need a master clock. The clock will be responsible for advancing all the active timelines created inside a scene, but only when the stage is being redrawn. The sequence is: + queue_redraw() is invoked on an actor and bubbles up to the stage + if no redraw() has already been scheduled, install an idle handler with a known priority + inside the idle handler: - advance the master clock, which will in turn advance every playing timeline by the amount of milliseconds elapsed since the last redraw; this will make every playing timeline emit the ::new-frame signal - queue a relayout - call the redraw() method of the backend This way we trade multiple timeouts with a single frame source that only runs if a timeline is playing and queues redraws on the various stages.
1861 lines
50 KiB
C
1861 lines
50 KiB
C
/*
|
|
* Clutter.
|
|
*
|
|
* An OpenGL based 'interactive canvas' library.
|
|
*
|
|
* Authored By Matthew Allum <mallum@openedhand.com>
|
|
*
|
|
* Copyright (C) 2006 OpenedHand
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:clutter-stage
|
|
* @short_description: Top level visual element to which actors are placed.
|
|
*
|
|
* #ClutterStage is a top level 'window' on which child actors are placed
|
|
* and manipulated.
|
|
*
|
|
* Clutter creates a default stage upon initialization, which can be retrieved
|
|
* using clutter_stage_get_default(). Clutter always provides the default
|
|
* stage, unless the backend is unable to create one. The stage returned
|
|
* by clutter_stage_get_default() is guaranteed to always be the same.
|
|
*
|
|
* Backends might provide support for multiple stages. The support for this
|
|
* feature can be checked at run-time using the clutter_feature_available()
|
|
* function and the %CLUTTER_FEATURE_STAGE_MULTIPLE flag. If the backend used
|
|
* supports multiple stages, new #ClutterStage instances can be created
|
|
* using clutter_stage_new(). These stages must be managed by the developer
|
|
* using clutter_actor_destroy(), which will take care of destroying all the
|
|
* actors contained inside them.
|
|
*
|
|
* #ClutterStage is a proxy actor, wrapping the backend-specific
|
|
* implementation of the windowing system. It is possible to subclass
|
|
* #ClutterStage, as long as every overridden virtual function chains up to
|
|
* the parent class corresponding function.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "clutter-backend.h"
|
|
#include "clutter-stage.h"
|
|
#include "clutter-main.h"
|
|
#include "clutter-color.h"
|
|
#include "clutter-util.h"
|
|
#include "clutter-marshal.h"
|
|
#include "clutter-master-clock.h"
|
|
#include "clutter-enum-types.h"
|
|
#include "clutter-private.h"
|
|
#include "clutter-debug.h"
|
|
#include "clutter-stage-manager.h"
|
|
#include "clutter-stage-window.h"
|
|
#include "clutter-version.h" /* For flavour */
|
|
#include "clutter-id-pool.h"
|
|
|
|
#include "cogl/cogl.h"
|
|
|
|
G_DEFINE_TYPE (ClutterStage, clutter_stage, CLUTTER_TYPE_GROUP);
|
|
|
|
#define CLUTTER_STAGE_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_STAGE, ClutterStagePrivate))
|
|
|
|
struct _ClutterStagePrivate
|
|
{
|
|
/* the stage implementation */
|
|
ClutterActor *impl;
|
|
|
|
ClutterColor color;
|
|
ClutterPerspective perspective;
|
|
ClutterFog fog;
|
|
|
|
gchar *title;
|
|
ClutterActor *key_focused_actor;
|
|
|
|
guint update_idle; /* repaint idler id */
|
|
|
|
guint is_fullscreen : 1;
|
|
guint is_offscreen : 1;
|
|
guint is_cursor_visible : 1;
|
|
guint is_user_resizable : 1;
|
|
guint use_fog : 1;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_COLOR,
|
|
PROP_FULLSCREEN,
|
|
PROP_OFFSCREEN,
|
|
PROP_CURSOR_VISIBLE,
|
|
PROP_PERSPECTIVE,
|
|
PROP_TITLE,
|
|
PROP_USER_RESIZE,
|
|
PROP_USE_FOG,
|
|
PROP_FOG
|
|
};
|
|
|
|
enum
|
|
{
|
|
FULLSCREEN,
|
|
UNFULLSCREEN,
|
|
ACTIVATE,
|
|
DEACTIVATE,
|
|
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint stage_signals[LAST_SIGNAL] = { 0, };
|
|
|
|
static const ClutterColor default_stage_color = { 255, 255, 255, 255 };
|
|
|
|
static void
|
|
clutter_stage_get_preferred_width (ClutterActor *self,
|
|
ClutterUnit for_height,
|
|
ClutterUnit *min_width_p,
|
|
ClutterUnit *natural_width_p)
|
|
{
|
|
ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
|
|
|
|
g_assert (priv->impl != NULL);
|
|
|
|
CLUTTER_ACTOR_GET_CLASS (priv->impl)->get_preferred_width (priv->impl,
|
|
for_height,
|
|
min_width_p,
|
|
natural_width_p);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_get_preferred_height (ClutterActor *self,
|
|
ClutterUnit for_width,
|
|
ClutterUnit *min_height_p,
|
|
ClutterUnit *natural_height_p)
|
|
{
|
|
ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
|
|
|
|
g_assert (priv->impl != NULL);
|
|
|
|
CLUTTER_ACTOR_GET_CLASS (priv->impl)->get_preferred_height (priv->impl,
|
|
for_width,
|
|
min_height_p,
|
|
natural_height_p);
|
|
}
|
|
static void
|
|
clutter_stage_allocate (ClutterActor *self,
|
|
const ClutterActorBox *box,
|
|
gboolean origin_changed)
|
|
{
|
|
ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
|
|
|
|
g_assert (priv->impl != NULL);
|
|
|
|
/* if the stage is fixed size (for instance, it's using a frame-buffer)
|
|
* then we simply ignore any allocation request and override the
|
|
* allocation chain.
|
|
*/
|
|
if (G_LIKELY (!clutter_feature_available (CLUTTER_FEATURE_STAGE_STATIC)))
|
|
{
|
|
ClutterActorClass *klass;
|
|
|
|
CLUTTER_NOTE (ACTOR, "Following allocation to %dx%d (origin %s)",
|
|
CLUTTER_UNITS_TO_DEVICE (box->x2 - box->x1),
|
|
CLUTTER_UNITS_TO_DEVICE (box->y2 - box->y1),
|
|
origin_changed ? "changed" : "not changed");
|
|
|
|
klass = CLUTTER_ACTOR_CLASS (clutter_stage_parent_class);
|
|
klass->allocate (self, box, origin_changed);
|
|
|
|
klass = CLUTTER_ACTOR_GET_CLASS (priv->impl);
|
|
klass->allocate (priv->impl, box, origin_changed);
|
|
}
|
|
else
|
|
{
|
|
ClutterActorBox override = { 0, };
|
|
ClutterActorClass *klass;
|
|
ClutterUnit natural_width, natural_height;
|
|
|
|
/* propagate the allocation */
|
|
klass = CLUTTER_ACTOR_GET_CLASS (priv->impl);
|
|
klass->allocate (self, box, origin_changed);
|
|
|
|
/* get the preferred size from the backend */
|
|
clutter_actor_get_preferred_size (priv->impl,
|
|
NULL, NULL,
|
|
&natural_width, &natural_height);
|
|
|
|
override.x1 = 0;
|
|
override.y1 = 0;
|
|
override.x2 = natural_width;
|
|
override.y2 = natural_height;
|
|
|
|
/* and store the overridden allocation */
|
|
klass = CLUTTER_ACTOR_CLASS (clutter_stage_parent_class);
|
|
klass->allocate (self, &override, origin_changed);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_stage_paint (ClutterActor *self)
|
|
{
|
|
ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
|
|
CoglColor stage_color;
|
|
|
|
CLUTTER_NOTE (PAINT, "Initializing stage paint");
|
|
|
|
cogl_color_set_from_4ub (&stage_color,
|
|
priv->color.red,
|
|
priv->color.green,
|
|
priv->color.blue,
|
|
priv->color.alpha);
|
|
cogl_clear (&stage_color);
|
|
|
|
if (priv->use_fog)
|
|
{
|
|
/* we only expose the linear progression of the fog in
|
|
* the ClutterStage API, and that ignores the fog density.
|
|
* thus, we pass 1.0 as the density parameter
|
|
*/
|
|
cogl_set_fog (&stage_color,
|
|
COGL_FOG_MODE_LINEAR,
|
|
1.0,
|
|
priv->fog.z_near,
|
|
priv->fog.z_far);
|
|
}
|
|
else
|
|
cogl_disable_fog ();
|
|
|
|
CLUTTER_NOTE (PAINT, "Proxying the paint to the stage implementation");
|
|
clutter_actor_paint (priv->impl);
|
|
|
|
/* this will take care of painting every child */
|
|
CLUTTER_ACTOR_CLASS (clutter_stage_parent_class)->paint (self);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_pick (ClutterActor *self,
|
|
const ClutterColor *color)
|
|
{
|
|
/* Paint nothing, cogl_paint_init() effectively paints the stage
|
|
* silhouette for us - see _clutter_do_pick().
|
|
* Chain up to the groups paint howerer so our children get picked
|
|
* - clutter_group_pick
|
|
*/
|
|
CLUTTER_ACTOR_CLASS (clutter_stage_parent_class)->paint (self);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_realize (ClutterActor *self)
|
|
{
|
|
ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
|
|
|
|
/* Make sure the viewport and projection matrix are valid for the
|
|
first paint (which will likely occur before the ConfigureNotify
|
|
is received) */
|
|
CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_SYNC_MATRICES);
|
|
|
|
g_assert (priv->impl != NULL);
|
|
clutter_actor_realize (priv->impl);
|
|
|
|
/* ensure that the stage is using the context if the
|
|
* realization sequence was successful
|
|
*/
|
|
if (CLUTTER_ACTOR_IS_REALIZED (priv->impl))
|
|
clutter_stage_ensure_current (CLUTTER_STAGE (self));
|
|
else
|
|
CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_REALIZED);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_unrealize (ClutterActor *self)
|
|
{
|
|
ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
|
|
|
|
/* and then unrealize the implementation */
|
|
g_assert (priv->impl != NULL);
|
|
clutter_actor_unrealize (priv->impl);
|
|
|
|
clutter_stage_ensure_current (CLUTTER_STAGE (self));
|
|
}
|
|
|
|
static void
|
|
clutter_stage_show (ClutterActor *self)
|
|
{
|
|
ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
|
|
|
|
g_assert (priv->impl != NULL);
|
|
|
|
clutter_actor_show (priv->impl);
|
|
|
|
CLUTTER_ACTOR_CLASS (clutter_stage_parent_class)->show (self);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_hide (ClutterActor *self)
|
|
{
|
|
ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
|
|
|
|
g_assert (priv->impl != NULL);
|
|
clutter_actor_hide (priv->impl);
|
|
|
|
CLUTTER_ACTOR_CLASS (clutter_stage_parent_class)->hide (self);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_real_fullscreen (ClutterStage *stage)
|
|
{
|
|
ClutterStagePrivate *priv = stage->priv;
|
|
ClutterUnit natural_width, natural_height;
|
|
ClutterActorBox box;
|
|
|
|
/* we need to force an allocation here because the size
|
|
* of the stage might have been changed by the backend
|
|
*
|
|
* this is a really bad solution to the issues caused by
|
|
* the fact that fullscreening the stage on the X11 backends
|
|
* is really an asynchronous operation
|
|
*/
|
|
clutter_actor_get_preferred_size (CLUTTER_ACTOR (priv->impl),
|
|
NULL, NULL,
|
|
&natural_width, &natural_height);
|
|
|
|
box.x1 = 0;
|
|
box.y1 = 0;
|
|
box.x2 = natural_width;
|
|
box.y2 = natural_height;
|
|
|
|
clutter_actor_allocate (CLUTTER_ACTOR (stage), &box, FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
redraw_update_idle (gpointer user_data)
|
|
{
|
|
ClutterStage *stage = user_data;
|
|
ClutterStagePrivate *priv = stage->priv;
|
|
ClutterMasterClock *master_clock;
|
|
gboolean retval = FALSE;
|
|
|
|
/* before we redraw we advance the master clock of one tick; this means
|
|
* that all the timelines that need advancing will be advanced by one
|
|
* frame. this will cause multiple redraw requests, so we do this before
|
|
* we ask for a relayout and before we do the actual redraw. this ensures
|
|
* that we paint the most updated scenegraph state and that all animations
|
|
* are in sync with the paint process.
|
|
*/
|
|
CLUTTER_NOTE (PAINT, "Avdancing master clock");
|
|
master_clock = _clutter_master_clock_get_default ();
|
|
_clutter_master_clock_advance (master_clock);
|
|
|
|
/* clutter_redraw() will also call maybe_relayout(), but since a relayout
|
|
* can queue a redraw, we want to do the relayout before we clear the
|
|
* update_idle to avoid painting the stage twice. Calling maybe_relayout()
|
|
* twice in a row is cheap because of caching of requested and allocated
|
|
* size.
|
|
*/
|
|
_clutter_stage_maybe_relayout (CLUTTER_ACTOR (stage));
|
|
|
|
CLUTTER_NOTE (PAINT, "redrawing via idle for stage[%p]", stage);
|
|
clutter_redraw (stage);
|
|
|
|
if (CLUTTER_CONTEXT ()->redraw_count > 0)
|
|
{
|
|
CLUTTER_NOTE (PAINT, "Queued %lu redraws during the last cycle",
|
|
CLUTTER_CONTEXT ()->redraw_count);
|
|
|
|
CLUTTER_CONTEXT ()->redraw_count = 0;
|
|
}
|
|
|
|
priv->update_idle = 0;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static void
|
|
clutter_stage_real_queue_redraw (ClutterActor *actor,
|
|
ClutterActor *leaf)
|
|
{
|
|
ClutterStage *stage = CLUTTER_STAGE (actor);
|
|
ClutterStagePrivate *priv = stage->priv;
|
|
|
|
CLUTTER_NOTE (PAINT, "Redraw request number %lu",
|
|
CLUTTER_CONTEXT ()->redraw_count + 1);
|
|
|
|
if (priv->update_idle == 0)
|
|
{
|
|
CLUTTER_NOTE (PAINT, "Adding idle source for stage: %p", stage);
|
|
|
|
priv->update_idle =
|
|
clutter_threads_add_idle_full (CLUTTER_PRIORITY_REDRAW,
|
|
redraw_update_idle,
|
|
stage,
|
|
NULL);
|
|
}
|
|
else
|
|
CLUTTER_CONTEXT ()->redraw_count += 1;
|
|
}
|
|
|
|
static void
|
|
set_offscreen_while_unrealized (ClutterActor *actor,
|
|
void *data)
|
|
{
|
|
CLUTTER_STAGE (actor)->priv->is_offscreen =
|
|
GPOINTER_TO_INT (data);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterStage *stage;
|
|
ClutterStagePrivate *priv;
|
|
ClutterActor *actor;
|
|
|
|
stage = CLUTTER_STAGE (object);
|
|
actor = CLUTTER_ACTOR (stage);
|
|
priv = stage->priv;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_COLOR:
|
|
clutter_stage_set_color (stage, clutter_value_get_color (value));
|
|
break;
|
|
|
|
case PROP_OFFSCREEN:
|
|
{
|
|
gboolean was_showing;
|
|
|
|
if (priv->is_offscreen == g_value_get_boolean (value))
|
|
return;
|
|
|
|
was_showing = CLUTTER_ACTOR_IS_VISIBLE (actor);
|
|
|
|
/* Backend needs to check this prop and handle accordingly
|
|
* in realise.
|
|
* FIXME: More 'obvious' implementation needed?
|
|
*/
|
|
_clutter_actor_rerealize (actor,
|
|
set_offscreen_while_unrealized,
|
|
GINT_TO_POINTER (g_value_get_boolean (value)));
|
|
|
|
if (was_showing &&
|
|
!CLUTTER_ACTOR_IS_REALIZED (actor))
|
|
priv->is_offscreen = ~g_value_get_boolean (value);
|
|
}
|
|
break;
|
|
|
|
case PROP_FULLSCREEN:
|
|
if (g_value_get_boolean (value))
|
|
clutter_stage_fullscreen (stage);
|
|
else
|
|
clutter_stage_unfullscreen (stage);
|
|
break;
|
|
|
|
case PROP_CURSOR_VISIBLE:
|
|
if (g_value_get_boolean (value))
|
|
clutter_stage_show_cursor (stage);
|
|
else
|
|
clutter_stage_hide_cursor (stage);
|
|
break;
|
|
|
|
case PROP_PERSPECTIVE:
|
|
clutter_stage_set_perspective (stage, g_value_get_boxed (value));
|
|
break;
|
|
|
|
case PROP_TITLE:
|
|
clutter_stage_set_title (stage, g_value_get_string (value));
|
|
break;
|
|
|
|
case PROP_USER_RESIZE:
|
|
clutter_stage_set_user_resizable (stage, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_USE_FOG:
|
|
clutter_stage_set_use_fog (stage, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_FOG:
|
|
clutter_stage_set_fog (stage, g_value_get_boxed (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_stage_get_property (GObject *gobject,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterStagePrivate *priv = CLUTTER_STAGE (gobject)->priv;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_COLOR:
|
|
clutter_value_set_color (value, &priv->color);
|
|
break;
|
|
|
|
case PROP_OFFSCREEN:
|
|
g_value_set_boolean (value, priv->is_offscreen);
|
|
break;
|
|
|
|
case PROP_FULLSCREEN:
|
|
g_value_set_boolean (value, priv->is_fullscreen);
|
|
break;
|
|
|
|
case PROP_CURSOR_VISIBLE:
|
|
g_value_set_boolean (value, priv->is_cursor_visible);
|
|
break;
|
|
|
|
case PROP_PERSPECTIVE:
|
|
g_value_set_boxed (value, &priv->perspective);
|
|
break;
|
|
|
|
case PROP_TITLE:
|
|
g_value_set_string (value, priv->title);
|
|
break;
|
|
|
|
case PROP_USER_RESIZE:
|
|
g_value_set_boolean (value, priv->is_user_resizable);
|
|
break;
|
|
|
|
case PROP_USE_FOG:
|
|
g_value_set_boolean (value, priv->use_fog);
|
|
break;
|
|
|
|
case PROP_FOG:
|
|
g_value_set_boxed (value, &priv->fog);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_stage_dispose (GObject *object)
|
|
{
|
|
ClutterStage *stage = CLUTTER_STAGE (object);
|
|
ClutterStagePrivate *priv = stage->priv;
|
|
ClutterStageManager *stage_manager = clutter_stage_manager_get_default ();
|
|
|
|
clutter_actor_hide (CLUTTER_ACTOR (object));
|
|
|
|
if (priv->update_idle)
|
|
{
|
|
g_source_remove (priv->update_idle);
|
|
priv->update_idle = 0;
|
|
}
|
|
|
|
_clutter_stage_manager_remove_stage (stage_manager, stage);
|
|
|
|
if (priv->impl)
|
|
{
|
|
CLUTTER_NOTE (MISC, "Disposing of the stage implementation");
|
|
g_object_unref (priv->impl);
|
|
priv->impl = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (clutter_stage_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_finalize (GObject *object)
|
|
{
|
|
ClutterStage *stage = CLUTTER_STAGE (object);
|
|
|
|
g_free (stage->priv->title);
|
|
|
|
G_OBJECT_CLASS (clutter_stage_parent_class)->finalize (object);
|
|
}
|
|
|
|
|
|
static void
|
|
clutter_stage_class_init (ClutterStageClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
|
|
GParamSpec *pspec;
|
|
|
|
gobject_class->set_property = clutter_stage_set_property;
|
|
gobject_class->get_property = clutter_stage_get_property;
|
|
gobject_class->dispose = clutter_stage_dispose;
|
|
gobject_class->finalize = clutter_stage_finalize;
|
|
|
|
actor_class->allocate = clutter_stage_allocate;
|
|
actor_class->get_preferred_width = clutter_stage_get_preferred_width;
|
|
actor_class->get_preferred_height = clutter_stage_get_preferred_height;
|
|
actor_class->paint = clutter_stage_paint;
|
|
actor_class->pick = clutter_stage_pick;
|
|
actor_class->realize = clutter_stage_realize;
|
|
actor_class->unrealize = clutter_stage_unrealize;
|
|
actor_class->show = clutter_stage_show;
|
|
actor_class->hide = clutter_stage_hide;
|
|
actor_class->queue_redraw = clutter_stage_real_queue_redraw;
|
|
|
|
/**
|
|
* ClutterStage:fullscreen:
|
|
*
|
|
* Whether the stage should be fullscreen or not.
|
|
*/
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_FULLSCREEN,
|
|
g_param_spec_boolean ("fullscreen",
|
|
"Fullscreen",
|
|
"Whether the main stage is fullscreen",
|
|
FALSE,
|
|
G_PARAM_CONSTRUCT | CLUTTER_PARAM_READWRITE));
|
|
/**
|
|
* ClutterStage:offscreen:
|
|
*
|
|
* Whether the stage should be rendered in an offscreen buffer.
|
|
*/
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_OFFSCREEN,
|
|
g_param_spec_boolean ("offscreen",
|
|
"Offscreen",
|
|
"Whether the main stage is renderer offscreen",
|
|
FALSE,
|
|
G_PARAM_CONSTRUCT | CLUTTER_PARAM_READWRITE));
|
|
/**
|
|
* ClutterStage:cursor-visible:
|
|
*
|
|
* Whether the mouse pointer should be visible
|
|
*/
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_CURSOR_VISIBLE,
|
|
g_param_spec_boolean ("cursor-visible",
|
|
"Cursor Visible",
|
|
"Whether the mouse pointer is visible on the main stage ",
|
|
TRUE,
|
|
G_PARAM_CONSTRUCT | CLUTTER_PARAM_READWRITE));
|
|
/**
|
|
* ClutterStage:user-resizable:
|
|
*
|
|
* Whether the stage is resizable via user interaction.
|
|
*
|
|
* Since: 0.4
|
|
*/
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_USER_RESIZE,
|
|
g_param_spec_boolean ("user-resizable",
|
|
"User Resizable",
|
|
"Whether the stage is able to be resized via "
|
|
"user interaction",
|
|
FALSE,
|
|
G_PARAM_CONSTRUCT | CLUTTER_PARAM_READWRITE));
|
|
/**
|
|
* ClutterStage:color:
|
|
*
|
|
* The color of the main stage.
|
|
*/
|
|
pspec = clutter_param_spec_color ("color",
|
|
"Color",
|
|
"The color of the stage",
|
|
&default_stage_color,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_COLOR, pspec);
|
|
|
|
/**
|
|
* ClutterStage:perspective:
|
|
*
|
|
* The parameters used for the perspective projection from 3D
|
|
* coordinates to 2D
|
|
*
|
|
* Since: 0.8.2
|
|
*/
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_PERSPECTIVE,
|
|
g_param_spec_boxed ("perspective",
|
|
"Perspective",
|
|
"Perspective projection parameters",
|
|
CLUTTER_TYPE_PERSPECTIVE,
|
|
CLUTTER_PARAM_READWRITE));
|
|
|
|
/**
|
|
* ClutterStage:title:
|
|
*
|
|
* The stage's title - usually displayed in stage windows title decorations.
|
|
*
|
|
* Since: 0.4
|
|
*/
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_TITLE,
|
|
g_param_spec_string ("title",
|
|
"Title",
|
|
"Stage Title",
|
|
NULL,
|
|
CLUTTER_PARAM_READWRITE));
|
|
/**
|
|
* ClutterStage:use-fog:
|
|
*
|
|
* Whether the stage should use a linear GL "fog" in creating the
|
|
* depth-cueing effect, to enhance the perception of depth by fading
|
|
* actors farther from the viewpoint.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_USE_FOG,
|
|
g_param_spec_boolean ("use-fog",
|
|
"Use Fog",
|
|
"Whether to enable depth cueing",
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE));
|
|
/**
|
|
* ClutterStage:fog:
|
|
*
|
|
* The settings for the GL "fog", used only if #ClutterStage:use-fog
|
|
* is set to %TRUE
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boxed ("fog",
|
|
"Fog",
|
|
"Settings for the depth cueing",
|
|
CLUTTER_TYPE_FOG,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_FOG, pspec);
|
|
|
|
/**
|
|
* ClutterStage::fullscreen
|
|
* @stage: the stage which was fullscreened
|
|
*
|
|
* The ::fullscreen signal is emitted when the stage is made fullscreen.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
stage_signals[FULLSCREEN] =
|
|
g_signal_new ("fullscreen",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (ClutterStageClass, fullscreen),
|
|
NULL, NULL,
|
|
clutter_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
/**
|
|
* ClutterStage::unfullscreen
|
|
* @stage: the stage which has left a fullscreen state.
|
|
*
|
|
* The ::unfullscreen signal is emitted when the stage leaves a fullscreen
|
|
* state.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
stage_signals[UNFULLSCREEN] =
|
|
g_signal_new ("unfullscreen",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterStageClass, unfullscreen),
|
|
NULL, NULL,
|
|
clutter_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
/**
|
|
* ClutterStage::activate
|
|
* @stage: the stage which was activated
|
|
*
|
|
* The ::activate signal is emitted when the stage receives key focus
|
|
* from the underlying window system.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
stage_signals[ACTIVATE] =
|
|
g_signal_new ("activate",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterStageClass, activate),
|
|
NULL, NULL,
|
|
clutter_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
/**
|
|
* ClutterStage::deactivate
|
|
* @stage: the stage which was deactivated
|
|
*
|
|
* The ::activate signal is emitted when the stage loses key focus
|
|
* from the underlying window system.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
stage_signals[DEACTIVATE] =
|
|
g_signal_new ("deactivate",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterStageClass, deactivate),
|
|
NULL, NULL,
|
|
clutter_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
klass->fullscreen = clutter_stage_real_fullscreen;
|
|
|
|
g_type_class_add_private (gobject_class, sizeof (ClutterStagePrivate));
|
|
}
|
|
|
|
static void
|
|
clutter_stage_init (ClutterStage *self)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
ClutterBackend *backend;
|
|
|
|
/* a stage is a top-level object */
|
|
CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_IS_TOPLEVEL);
|
|
|
|
self->priv = priv = CLUTTER_STAGE_GET_PRIVATE (self);
|
|
|
|
CLUTTER_NOTE (BACKEND, "Creating stage from the default backend");
|
|
backend = clutter_get_default_backend ();
|
|
priv->impl = _clutter_backend_create_stage (backend, self, NULL);
|
|
if (!priv->impl)
|
|
{
|
|
g_warning ("Unable to create a new stage, falling back to the "
|
|
"default stage.");
|
|
priv->impl = CLUTTER_ACTOR (_clutter_stage_get_default_window ());
|
|
|
|
/* at this point we must have a default stage, or we're screwed */
|
|
g_assert (priv->impl != NULL);
|
|
}
|
|
else
|
|
g_object_ref_sink (priv->impl);
|
|
|
|
priv->is_offscreen = FALSE;
|
|
priv->is_fullscreen = FALSE;
|
|
priv->is_user_resizable = FALSE;
|
|
priv->is_cursor_visible = TRUE;
|
|
priv->use_fog = FALSE;
|
|
|
|
priv->color = default_stage_color;
|
|
|
|
priv->perspective.fovy = 60.0; /* 60 Degrees */
|
|
priv->perspective.aspect = 1.0;
|
|
priv->perspective.z_near = 0.1;
|
|
priv->perspective.z_far = 100.0;
|
|
|
|
/* depth cueing */
|
|
priv->fog.z_near = 1.0;
|
|
priv->fog.z_far = 2.0;
|
|
|
|
clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE);
|
|
clutter_stage_set_key_focus (self, NULL);
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_get_default:
|
|
*
|
|
* Returns the main stage. The default #ClutterStage is a singleton,
|
|
* so the stage will be created the first time this function is
|
|
* called (typically, inside clutter_init()); all the subsequent
|
|
* calls to clutter_stage_get_default() will return the same instance.
|
|
*
|
|
* Clutter guarantess the existence of the default stage.
|
|
*
|
|
* Return value: (transfer none): the main #ClutterStage. You should never
|
|
* destroy or unref the returned actor.
|
|
*/
|
|
ClutterActor *
|
|
clutter_stage_get_default (void)
|
|
{
|
|
ClutterStageManager *stage_manager = clutter_stage_manager_get_default ();
|
|
ClutterStage *stage;
|
|
|
|
stage = clutter_stage_manager_get_default_stage (stage_manager);
|
|
if (G_UNLIKELY (stage == NULL))
|
|
/* This will take care of automatically adding the stage to the
|
|
* stage manager and setting it as the default. Its floating
|
|
* reference will be claimed by the stage manager.
|
|
*/
|
|
stage = g_object_new (CLUTTER_TYPE_STAGE, NULL);
|
|
|
|
return CLUTTER_ACTOR (stage);
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_set_color:
|
|
* @stage: A #ClutterStage
|
|
* @color: A #ClutterColor
|
|
*
|
|
* Sets the stage color.
|
|
*/
|
|
void
|
|
clutter_stage_set_color (ClutterStage *stage,
|
|
const ClutterColor *color)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
g_return_if_fail (color != NULL);
|
|
|
|
priv = stage->priv;
|
|
|
|
priv->color = *color;
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (stage))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
|
|
|
|
g_object_notify (G_OBJECT (stage), "color");
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_get_color:
|
|
* @stage: A #ClutterStage
|
|
* @color: return location for a #ClutterColor
|
|
*
|
|
* Retrieves the stage color.
|
|
*/
|
|
void
|
|
clutter_stage_get_color (ClutterStage *stage,
|
|
ClutterColor *color)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
g_return_if_fail (color != NULL);
|
|
|
|
priv = stage->priv;
|
|
|
|
*color = priv->color;
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_set_perspective:
|
|
* @stage: A #ClutterStage
|
|
* @perspective: A #ClutterPerspective
|
|
*
|
|
* Sets the stage perspective.
|
|
*/
|
|
void
|
|
clutter_stage_set_perspective (ClutterStage *stage,
|
|
ClutterPerspective *perspective)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
g_return_if_fail (perspective != NULL);
|
|
g_return_if_fail (perspective->z_far - perspective->z_near != 0);
|
|
|
|
priv = stage->priv;
|
|
|
|
priv->perspective = *perspective;
|
|
|
|
/* this will cause the viewport to be reset; see
|
|
* clutter_maybe_setup_viewport() inside clutter-main.c
|
|
*/
|
|
CLUTTER_SET_PRIVATE_FLAGS (stage, CLUTTER_ACTOR_SYNC_MATRICES);
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_get_perspective:
|
|
* @stage: A #ClutterStage
|
|
* @perspective: return location for a #ClutterPerspective
|
|
*
|
|
* Retrieves the stage perspective.
|
|
*/
|
|
void
|
|
clutter_stage_get_perspective (ClutterStage *stage,
|
|
ClutterPerspective *perspective)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
g_return_if_fail (perspective != NULL);
|
|
|
|
*perspective = stage->priv->perspective;
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_fullscreen:
|
|
* @stage: a #ClutterStage
|
|
*
|
|
* Asks to place the stage window in the fullscreen state. Note that you
|
|
* shouldn't assume the window is definitely full screen afterward, because
|
|
* other entities (e.g. the user or window manager) could unfullscreen it
|
|
* again, and not all window managers honor requests to fullscreen windows.
|
|
*/
|
|
void
|
|
clutter_stage_fullscreen (ClutterStage *stage)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
|
|
priv = stage->priv;
|
|
if (!priv->is_fullscreen)
|
|
{
|
|
ClutterStageWindow *impl = CLUTTER_STAGE_WINDOW (priv->impl);
|
|
ClutterStageWindowIface *iface;
|
|
|
|
iface = CLUTTER_STAGE_WINDOW_GET_IFACE (impl);
|
|
|
|
/* Only set if backend implements.
|
|
* Also see clutter_stage_event() for setting priv->is_fullscreen
|
|
* on state change event.
|
|
*/
|
|
if (iface->set_fullscreen)
|
|
iface->set_fullscreen (impl, TRUE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_unfullscreen:
|
|
* @stage: a #ClutterStage
|
|
*
|
|
* Asks to toggle off the fullscreen state for the stage window. Note that
|
|
* you shouldn't assume the window is definitely not full screen afterward,
|
|
* because other entities (e.g. the user or window manager) could fullscreen
|
|
* it again, and not all window managers honor requests to unfullscreen
|
|
* windows.
|
|
*/
|
|
void
|
|
clutter_stage_unfullscreen (ClutterStage *stage)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
|
|
priv = stage->priv;
|
|
if (priv->is_fullscreen)
|
|
{
|
|
ClutterStageWindow *impl = CLUTTER_STAGE_WINDOW (priv->impl);
|
|
ClutterStageWindowIface *iface;
|
|
|
|
iface = CLUTTER_STAGE_WINDOW_GET_IFACE (impl);
|
|
|
|
/* Only set if backend implements.
|
|
* Also see clutter_stage_event() for setting priv->is_fullscreen
|
|
* on state change event.
|
|
*/
|
|
if (iface->set_fullscreen)
|
|
iface->set_fullscreen (impl, FALSE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_set_user_resizable:
|
|
* @stage: a #ClutterStage
|
|
* @resizable: whether the stage should be user resizable.
|
|
*
|
|
* Sets if the stage is resizable by user interaction (e.g. via
|
|
* window manager controls)
|
|
*
|
|
* Since: 0.4
|
|
*/
|
|
void
|
|
clutter_stage_set_user_resizable (ClutterStage *stage,
|
|
gboolean resizable)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
|
|
priv = stage->priv;
|
|
|
|
if (clutter_feature_available (CLUTTER_FEATURE_STAGE_USER_RESIZE)
|
|
&& priv->is_user_resizable != resizable)
|
|
{
|
|
ClutterStageWindow *impl = CLUTTER_STAGE_WINDOW (priv->impl);
|
|
ClutterStageWindowIface *iface;
|
|
|
|
iface = CLUTTER_STAGE_WINDOW_GET_IFACE (impl);
|
|
if (iface->set_user_resizable)
|
|
{
|
|
priv->is_user_resizable = resizable;
|
|
|
|
iface->set_user_resizable (impl, resizable);
|
|
|
|
g_object_notify (G_OBJECT (stage), "user-resizable");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_get_user_resizable:
|
|
* @stage: a #ClutterStage
|
|
*
|
|
* Retrieves the value set with clutter_stage_set_user_resizable().
|
|
*
|
|
* Return value: %TRUE if the stage is resizable by the user.
|
|
*
|
|
* Since: 0.4
|
|
*/
|
|
gboolean
|
|
clutter_stage_get_user_resizable (ClutterStage *stage)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE);
|
|
|
|
return stage->priv->is_user_resizable;
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_show_cursor:
|
|
* @stage: a #ClutterStage
|
|
*
|
|
* Shows the cursor on the stage window
|
|
*/
|
|
void
|
|
clutter_stage_show_cursor (ClutterStage *stage)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
|
|
priv = stage->priv;
|
|
if (!priv->is_cursor_visible)
|
|
{
|
|
ClutterStageWindow *impl = CLUTTER_STAGE_WINDOW (priv->impl);
|
|
ClutterStageWindowIface *iface;
|
|
|
|
iface = CLUTTER_STAGE_WINDOW_GET_IFACE (impl);
|
|
if (iface->set_cursor_visible)
|
|
{
|
|
priv->is_cursor_visible = TRUE;
|
|
|
|
iface->set_cursor_visible (impl, TRUE);
|
|
|
|
g_object_notify (G_OBJECT (stage), "cursor-visible");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_hide_cursor:
|
|
* @stage: a #ClutterStage
|
|
*
|
|
* Makes the cursor invisible on the stage window
|
|
*
|
|
* Since: 0.4
|
|
*/
|
|
void
|
|
clutter_stage_hide_cursor (ClutterStage *stage)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
|
|
priv = stage->priv;
|
|
if (priv->is_cursor_visible)
|
|
{
|
|
ClutterStageWindow *impl = CLUTTER_STAGE_WINDOW (priv->impl);
|
|
ClutterStageWindowIface *iface;
|
|
|
|
iface = CLUTTER_STAGE_WINDOW_GET_IFACE (impl);
|
|
if (iface->set_cursor_visible)
|
|
{
|
|
priv->is_cursor_visible = FALSE;
|
|
|
|
iface->set_cursor_visible (impl, FALSE);
|
|
|
|
g_object_notify (G_OBJECT (stage), "cursor-visible");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_read_pixels:
|
|
* @stage: A #ClutterStage
|
|
* @x: x coordinate of the first pixel that is read from stage
|
|
* @y: y coordinate of the first pixel that is read from stage
|
|
* @width: Width dimention of pixels to be read, or -1 for the
|
|
* entire stage width
|
|
* @height: Height dimention of pixels to be read, or -1 for the
|
|
* entire stage height
|
|
*
|
|
* Makes a screenshot of the stage in RGBA 8bit data, returns a
|
|
* linear buffer with @width * 4 as rowstride.
|
|
*
|
|
* The alpha data contained in the returned buffer is driver-dependent,
|
|
* and not guaranteed to hold any sensible value.
|
|
*
|
|
* Return value: a pointer to newly allocated memory with the buffer
|
|
* or %NULL if the read failed. Use g_free() on the returned data
|
|
* to release the resources it has allocated.
|
|
*/
|
|
guchar *
|
|
clutter_stage_read_pixels (ClutterStage *stage,
|
|
gint x,
|
|
gint y,
|
|
gint width,
|
|
gint height)
|
|
{
|
|
guchar *pixels;
|
|
guchar *temprow;
|
|
GLint viewport[4];
|
|
gint rowstride;
|
|
gint stage_x, stage_y, stage_width, stage_height;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
|
|
|
|
/* according to glReadPixels documentation pixels outside the viewport are
|
|
* undefined, but no error should be provoked, thus this is probably unnneed.
|
|
*/
|
|
g_return_val_if_fail (x >= 0 && y >= 0, NULL);
|
|
|
|
/* Force a redraw of the stage before reading back pixels */
|
|
clutter_redraw (stage);
|
|
clutter_stage_ensure_current (stage);
|
|
|
|
glGetIntegerv (GL_VIEWPORT, viewport);
|
|
stage_x = viewport[0];
|
|
stage_y = viewport[1];
|
|
stage_width = viewport[2];
|
|
stage_height = viewport[3];
|
|
|
|
if (width < 0 || width > stage_width)
|
|
width = stage_width;
|
|
|
|
if (height < 0 || height > stage_height)
|
|
height = stage_height;
|
|
|
|
rowstride = width * 4;
|
|
|
|
pixels = g_malloc (height * rowstride);
|
|
temprow = g_malloc (rowstride);
|
|
|
|
/* Setup the pixel store parameters that may have been changed by
|
|
Cogl */
|
|
glPixelStorei (GL_PACK_ALIGNMENT, 4);
|
|
#ifdef HAVE_COGL_GL
|
|
glPixelStorei (GL_PACK_ROW_LENGTH, 0);
|
|
glPixelStorei (GL_PACK_SKIP_PIXELS, 0);
|
|
glPixelStorei (GL_PACK_SKIP_ROWS, 0);
|
|
#endif /* HAVE_COGL_GL */
|
|
|
|
/* The y co-ordinate should be given in OpenGL's coordinate system
|
|
so 0 is the bottom row */
|
|
y = stage_height - y - height;
|
|
|
|
glFinish ();
|
|
|
|
/* check whether we need to read into a smaller temporary buffer */
|
|
glReadPixels (x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
|
|
|
/* vertically flip the buffer in-place */
|
|
for (y = 0; y < height / 2; y++)
|
|
{
|
|
if (y != height - y - 1) /* skip center row */
|
|
{
|
|
memcpy (temprow,
|
|
pixels + y * rowstride, rowstride);
|
|
memcpy (pixels + y * rowstride,
|
|
pixels + (height - y - 1) * rowstride, rowstride);
|
|
memcpy (pixels + (height - y - 1) * rowstride,
|
|
temprow,
|
|
rowstride);
|
|
}
|
|
}
|
|
|
|
g_free (temprow);
|
|
|
|
return pixels;
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_get_actor_at_pos:
|
|
* @stage: a #ClutterStage
|
|
* @pick_mode: how the scene graph should be painted
|
|
* @x: X coordinate to check
|
|
* @y: Y coordinate to check
|
|
*
|
|
* Checks the scene at the coordinates @x and @y and returns a pointer
|
|
* to the #ClutterActor at those coordinates.
|
|
*
|
|
* By using @pick_mode it is possible to control which actors will be
|
|
* painted and thus available.
|
|
*
|
|
* Return value: (transfer none): the actor at the specified coordinates,
|
|
* if any
|
|
*/
|
|
ClutterActor *
|
|
clutter_stage_get_actor_at_pos (ClutterStage *stage,
|
|
ClutterPickMode pick_mode,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
return _clutter_do_pick (stage, x, y, pick_mode);
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_event:
|
|
* @stage: a #ClutterStage
|
|
* @event: a #ClutterEvent
|
|
*
|
|
* This function is used to emit an event on the main stage.
|
|
*
|
|
* You should rarely need to use this function, except for
|
|
* synthetised events.
|
|
*
|
|
* Return value: the return value from the signal emission
|
|
*
|
|
* Since: 0.4
|
|
*/
|
|
gboolean
|
|
clutter_stage_event (ClutterStage *stage,
|
|
ClutterEvent *event)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE);
|
|
g_return_val_if_fail (event != NULL, FALSE);
|
|
|
|
priv = stage->priv;
|
|
|
|
if (event->type == CLUTTER_DELETE)
|
|
{
|
|
gboolean retval = FALSE;
|
|
|
|
g_signal_emit_by_name (stage, "event", event, &retval);
|
|
|
|
return retval;
|
|
}
|
|
|
|
if (event->type != CLUTTER_STAGE_STATE)
|
|
return FALSE;
|
|
|
|
/* emit raw event */
|
|
if (clutter_actor_event (CLUTTER_ACTOR (stage), event, FALSE))
|
|
return TRUE;
|
|
|
|
if (event->stage_state.changed_mask & CLUTTER_STAGE_STATE_FULLSCREEN)
|
|
{
|
|
if (event->stage_state.new_state & CLUTTER_STAGE_STATE_FULLSCREEN)
|
|
{
|
|
priv->is_fullscreen = TRUE;
|
|
g_signal_emit (stage, stage_signals[FULLSCREEN], 0);
|
|
|
|
g_object_notify (G_OBJECT (stage), "fullscreen");
|
|
}
|
|
else
|
|
{
|
|
priv->is_fullscreen = FALSE;
|
|
g_signal_emit (stage, stage_signals[UNFULLSCREEN], 0);
|
|
|
|
g_object_notify (G_OBJECT (stage), "fullscreen");
|
|
}
|
|
}
|
|
|
|
if (event->stage_state.changed_mask & CLUTTER_STAGE_STATE_ACTIVATED)
|
|
{
|
|
if (event->stage_state.new_state & CLUTTER_STAGE_STATE_ACTIVATED)
|
|
g_signal_emit (stage, stage_signals[ACTIVATE], 0);
|
|
else
|
|
g_signal_emit (stage, stage_signals[DEACTIVATE], 0);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_set_title
|
|
* @stage: A #ClutterStage
|
|
* @title: A utf8 string for the stage windows title.
|
|
*
|
|
* Sets the stage title.
|
|
*
|
|
* Since: 0.4
|
|
**/
|
|
void
|
|
clutter_stage_set_title (ClutterStage *stage,
|
|
const gchar *title)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
ClutterStageWindow *impl;
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
|
|
priv = stage->priv;
|
|
|
|
g_free (priv->title);
|
|
priv->title = g_strdup (title);
|
|
|
|
impl = CLUTTER_STAGE_WINDOW (priv->impl);
|
|
if (CLUTTER_STAGE_WINDOW_GET_IFACE(impl)->set_title != NULL)
|
|
CLUTTER_STAGE_WINDOW_GET_IFACE (impl)->set_title (impl, priv->title);
|
|
|
|
g_object_notify (G_OBJECT (stage), "title");
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_get_title
|
|
* @stage: A #ClutterStage
|
|
*
|
|
* Gets the stage title.
|
|
*
|
|
* Return value: pointer to the title string for the stage. The
|
|
* returned string is owned by the actor and should not
|
|
* be modified or freed.
|
|
*
|
|
* Since: 0.4
|
|
**/
|
|
G_CONST_RETURN gchar *
|
|
clutter_stage_get_title (ClutterStage *stage)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
|
|
|
|
return stage->priv->title;
|
|
}
|
|
|
|
static void
|
|
on_key_focused_weak_notify (gpointer data,
|
|
GObject *where_the_object_was)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
ClutterStage *stage = CLUTTER_STAGE (data);
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
|
|
priv = stage->priv;
|
|
priv->key_focused_actor = NULL;
|
|
|
|
/* focused actor has dissapeared - fall back to stage
|
|
* FIXME: need some kind of signal dance/block here.
|
|
*/
|
|
clutter_stage_set_key_focus (stage, NULL);
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_set_key_focus:
|
|
* @stage: the #ClutterStage
|
|
* @actor: the actor to set key focus to, or %NULL
|
|
*
|
|
* Sets the key focus on @actor. An actor with key focus will receive
|
|
* all the key events. If @actor is %NULL, the stage will receive
|
|
* focus.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
void
|
|
clutter_stage_set_key_focus (ClutterStage *stage,
|
|
ClutterActor *actor)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
g_return_if_fail (actor == NULL || CLUTTER_IS_ACTOR (actor));
|
|
|
|
priv = stage->priv;
|
|
|
|
if (priv->key_focused_actor == actor)
|
|
return;
|
|
|
|
if (priv->key_focused_actor)
|
|
{
|
|
g_object_weak_unref (G_OBJECT (priv->key_focused_actor),
|
|
on_key_focused_weak_notify,
|
|
stage);
|
|
g_signal_emit_by_name (priv->key_focused_actor, "focus-out");
|
|
|
|
priv->key_focused_actor = NULL;
|
|
}
|
|
else
|
|
g_signal_emit_by_name (stage, "focus-out");
|
|
|
|
if (actor)
|
|
{
|
|
priv->key_focused_actor = actor;
|
|
|
|
g_object_weak_ref (G_OBJECT (actor),
|
|
on_key_focused_weak_notify,
|
|
stage);
|
|
g_signal_emit_by_name (priv->key_focused_actor, "focus-in");
|
|
}
|
|
else
|
|
g_signal_emit_by_name (stage, "focus-in");
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_get_key_focus:
|
|
* @stage: the #ClutterStage
|
|
*
|
|
* Retrieves the actor that is currently under key focus.
|
|
*
|
|
* Return value: (transfer none): the actor with key focus, or the stage
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
ClutterActor *
|
|
clutter_stage_get_key_focus (ClutterStage *stage)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
|
|
|
|
if (stage->priv->key_focused_actor)
|
|
return stage->priv->key_focused_actor;
|
|
|
|
return CLUTTER_ACTOR (stage);
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_get_use_fog:
|
|
* @stage: the #ClutterStage
|
|
*
|
|
* Gets whether the depth cueing effect is enabled on @stage.
|
|
*
|
|
* Return value: %TRUE if the the depth cueing effect is enabled
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
gboolean
|
|
clutter_stage_get_use_fog (ClutterStage *stage)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE);
|
|
|
|
return stage->priv->use_fog;
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_set_use_fog:
|
|
* @stage: the #ClutterStage
|
|
* @fog: %TRUE for enabling the depth cueing effect
|
|
*
|
|
* Sets whether the depth cueing effect on the stage should be enabled
|
|
* or not.
|
|
*
|
|
* Depth cueing is a 3D effect that makes actors farther away from the
|
|
* viewing point less opaque, by fading them with the stage color.
|
|
|
|
* The parameters of the GL fog used can be changed using the
|
|
* clutter_stage_set_fog() function.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
void
|
|
clutter_stage_set_use_fog (ClutterStage *stage,
|
|
gboolean fog)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
|
|
priv = stage->priv;
|
|
|
|
if (priv->use_fog != fog)
|
|
{
|
|
priv->use_fog = fog;
|
|
|
|
CLUTTER_NOTE (MISC, "%s depth-cueing inside stage",
|
|
priv->use_fog ? "enabling" : "disabling");
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (stage))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
|
|
|
|
g_object_notify (G_OBJECT (stage), "use-fog");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_set_fog:
|
|
* @stage: the #ClutterStage
|
|
* @fog: a #ClutterFog structure
|
|
*
|
|
* Sets the fog (also known as "depth cueing") settings for the @stage.
|
|
*
|
|
* A #ClutterStage will only use a linear fog progression, which
|
|
* depends solely on the distance from the viewer. The cogl_set_fog()
|
|
* function in COGL exposes more of the underlying implementation,
|
|
* and allows changing the for progression function. It can be directly
|
|
* used by disabling the #ClutterStage:use-fog property and connecting
|
|
* a signal handler to the #ClutterActor::paint signal on the @stage,
|
|
* like:
|
|
*
|
|
* |[
|
|
* clutter_stage_set_use_fog (stage, FALSE);
|
|
* g_signal_connect (stage, "paint", G_CALLBACK (on_stage_paint), NULL);
|
|
* ]|
|
|
*
|
|
* The paint signal handler will call cogl_set_fog() with the
|
|
* desired settings:
|
|
*
|
|
* |[
|
|
* static void
|
|
* on_stage_paint (ClutterActor *actor)
|
|
* {
|
|
* ClutterColor stage_color = { 0, };
|
|
* CoglColor fog_color = { 0, };
|
|
*
|
|
* /* set the fog color to the stage background color */
|
|
* clutter_stage_get_color (CLUTTER_STAGE (actor), &stage_color);
|
|
* cogl_color_set_from_4ub (&fog_color,
|
|
* stage_color.red,
|
|
* stage_color.green,
|
|
* stage_color.blue,
|
|
* stage_color.alpha);
|
|
*
|
|
* /* enable fog */
|
|
* cogl_set_fog (&fog_color,
|
|
* COGL_FOG_MODE_EXPONENTIAL, /* mode */
|
|
* 0.5, /* density */
|
|
* 5.0, 30.0); /* z_near and z_far */
|
|
* }
|
|
* ]|
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
void
|
|
clutter_stage_set_fog (ClutterStage *stage,
|
|
ClutterFog *fog)
|
|
{
|
|
ClutterStagePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
g_return_if_fail (fog != NULL);
|
|
|
|
priv = stage->priv;
|
|
|
|
priv->fog = *fog;
|
|
|
|
if (priv->use_fog && CLUTTER_ACTOR_IS_VISIBLE (stage))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_get_fog:
|
|
* @stage: the #ClutterStage
|
|
* @fog: return location for a #ClutterFog structure
|
|
*
|
|
* Retrieves the current depth cueing settings from the stage.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
void
|
|
clutter_stage_get_fog (ClutterStage *stage,
|
|
ClutterFog *fog)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
g_return_if_fail (fog != NULL);
|
|
|
|
*fog = stage->priv->fog;
|
|
}
|
|
|
|
/*** Perspective boxed type ******/
|
|
|
|
static gpointer
|
|
clutter_perspective_copy (gpointer data)
|
|
{
|
|
if (G_LIKELY (data))
|
|
return g_slice_dup (ClutterPerspective, data);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
clutter_perspective_free (gpointer data)
|
|
{
|
|
if (G_LIKELY (data))
|
|
g_slice_free (ClutterPerspective, data);
|
|
}
|
|
|
|
GType
|
|
clutter_perspective_get_type (void)
|
|
{
|
|
static GType our_type = 0;
|
|
|
|
if (!our_type)
|
|
our_type = g_boxed_type_register_static (I_("ClutterPerspective"),
|
|
clutter_perspective_copy,
|
|
clutter_perspective_free);
|
|
return our_type;
|
|
}
|
|
|
|
static gpointer
|
|
clutter_fog_copy (gpointer data)
|
|
{
|
|
if (G_LIKELY (data))
|
|
return g_slice_dup (ClutterFog, data);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
clutter_fog_free (gpointer data)
|
|
{
|
|
if (G_LIKELY (data))
|
|
g_slice_free (ClutterFog, data);
|
|
}
|
|
|
|
GType
|
|
clutter_fog_get_type (void)
|
|
{
|
|
static GType our_type = 0;
|
|
|
|
if (G_UNLIKELY (our_type == 0))
|
|
our_type = g_boxed_type_register_static (I_("ClutterFog"),
|
|
clutter_fog_copy,
|
|
clutter_fog_free);
|
|
|
|
return our_type;
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_new:
|
|
*
|
|
* Creates a new, non-default stage. A non-default stage is a new
|
|
* top-level actor which can be used as another container. It works
|
|
* exactly like the default stage, but while clutter_stage_get_default()
|
|
* will always return the same instance, you will have to keep a pointer
|
|
* to any #ClutterStage returned by clutter_stage_create().
|
|
*
|
|
* The ability to support multiple stages depends on the current
|
|
* backend. Use clutter_feature_available() and
|
|
* %CLUTTER_FEATURE_STAGE_MULTIPLE to check at runtime whether a
|
|
* backend supports multiple stages.
|
|
*
|
|
* Return value: a new stage, or %NULL if the default backend does
|
|
* not support multiple stages. Use clutter_actor_destroy() to
|
|
* programmatically close the returned stage.
|
|
*
|
|
* Since: 0.8
|
|
*/
|
|
ClutterActor *
|
|
clutter_stage_new (void)
|
|
{
|
|
if (!clutter_feature_available (CLUTTER_FEATURE_STAGE_MULTIPLE))
|
|
{
|
|
g_warning ("Unable to create a new stage: the %s backend does not "
|
|
"support multiple stages.",
|
|
CLUTTER_FLAVOUR);
|
|
return NULL;
|
|
}
|
|
|
|
/* The stage manager will grab the floating reference when the stage
|
|
is added to it in the constructor */
|
|
return g_object_new (CLUTTER_TYPE_STAGE, NULL);
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_ensure_current:
|
|
* @stage: the #ClutterStage
|
|
*
|
|
* This function essentially makes sure the right GL context is
|
|
* current for the passed stage. It is not intended to
|
|
* be used by applications.
|
|
*
|
|
* Since: 0.8
|
|
*/
|
|
void
|
|
clutter_stage_ensure_current (ClutterStage *stage)
|
|
{
|
|
ClutterMainContext *ctx = clutter_context_get_default ();
|
|
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
|
|
_clutter_backend_ensure_context (ctx->backend, stage);
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_ensure_viewport:
|
|
* @stage: a #ClutterStage
|
|
*
|
|
* Ensures that the GL viewport is updated with the current
|
|
* stage window size.
|
|
*
|
|
* This function will queue a redraw of @stage.
|
|
*
|
|
* This function should not be called by applications; it is used
|
|
* when embedding a #ClutterStage into a toolkit with another
|
|
* windowing system, like GTK+.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_stage_ensure_viewport (ClutterStage *stage)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
|
|
CLUTTER_SET_PRIVATE_FLAGS (stage, CLUTTER_ACTOR_SYNC_MATRICES);
|
|
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_queue_redraw:
|
|
* @stage: the #ClutterStage
|
|
*
|
|
* Queues a redraw for the passed stage.
|
|
*
|
|
* <note>Applications should call clutter_actor_queue_redraw() and not
|
|
* this function.</note>
|
|
*
|
|
* <note>This function is just a wrapper for clutter_actor_queue_redraw()
|
|
* and should probably go away.</note>
|
|
*
|
|
* Since: 0.8
|
|
*/
|
|
void
|
|
clutter_stage_queue_redraw (ClutterStage *stage)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_is_default:
|
|
* @stage: a #ClutterStage
|
|
*
|
|
* Checks if @stage is the default stage, or an instance created using
|
|
* clutter_stage_new() but internally using the same implementation.
|
|
*
|
|
* Return value: %TRUE if the passed stage is the default one
|
|
*
|
|
* Since: 0.8
|
|
*/
|
|
gboolean
|
|
clutter_stage_is_default (ClutterStage *stage)
|
|
{
|
|
ClutterStageWindow *impl;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE);
|
|
|
|
if (CLUTTER_ACTOR (stage) == clutter_stage_get_default ())
|
|
return TRUE;
|
|
|
|
impl = _clutter_stage_get_window (stage);
|
|
if (impl == _clutter_stage_get_default_window ())
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
_clutter_stage_set_window (ClutterStage *stage,
|
|
ClutterStageWindow *stage_window)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_STAGE (stage));
|
|
g_return_if_fail (CLUTTER_IS_STAGE_WINDOW (stage_window));
|
|
|
|
if (stage->priv->impl)
|
|
g_object_unref (stage->priv->impl);
|
|
|
|
stage->priv->impl = CLUTTER_ACTOR (stage_window);
|
|
}
|
|
|
|
ClutterStageWindow *
|
|
_clutter_stage_get_window (ClutterStage *stage)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
|
|
|
|
return CLUTTER_STAGE_WINDOW (stage->priv->impl);
|
|
}
|
|
|
|
ClutterStageWindow *
|
|
_clutter_stage_get_default_window (void)
|
|
{
|
|
ClutterActor *stage = clutter_stage_get_default ();
|
|
|
|
return _clutter_stage_get_window (CLUTTER_STAGE (stage));
|
|
}
|