From 125bded81455df73b37ed459f360ced0135db65a Mon Sep 17 00:00:00 2001 From: Havoc Pennington <hp@pobox.com> Date: Thu, 2 Apr 2009 09:16:43 -0400 Subject: [PATCH] Enforce invariants on mapped, realized, visibility states Bug 1138 - No trackable "mapped" state * Add a VISIBLE flag tracking application programmer's expected showing-state for the actor, allowing us to always ensure we keep what the app wants while tracking internal implementation state separately. * Make MAPPED reflect whether the actor will be painted; add notification on a ClutterActor::mapped property. Keep MAPPED state updated as the actor is shown, ancestors are shown, actor is reparented, etc. * Require a stage and realized parents to realize; this means at realization time the correct window system and GL resources are known. But unparented actors can no longer be realized. * Allow children to be unrealized even if parent is realized. Otherwise in effect either all actors or no actors are realized, i.e. it becomes a stage-global flag. * Allow clutter_actor_realize() to "fail" if not inside a toplevel * Rework clutter_actor_unrealize() so internally we have a flavor that does not mess with visibility flag * Add _clutter_actor_rerealize() to encapsulate a somewhat tricky operation we were doing in a couple of places * Do not realize/unrealize children in ClutterGroup, ClutterActor already does it * Do not realize impl by hand in clutter_stage_show(), since showing impl already does that * Do not unrealize in various dispose() methods, since ClutterActor dispose implementation already does it and chaining up is mandatory * ClutterTexture uses COGL while unrealizable (before it's added to a stage). Previously this breakage was affecting ClutterActor because we had to allow realize outside a stage. Move the breakage to ClutterTexture, by making ClutterTexture just use COGL while not realized. * Unrealize before we set parent to NULL in clutter_actor_unparent(). This means unrealize() implementations can get to the stage. Because actors need the stage in order to detach from stage. * Update clutter-actor-invariants.txt to reflect latest changes * Remove explicit hide/unrealize from ClutterActor::dispose since unparent already forces those Instead just assert that unparent() occurred and did the right thing. * Check whether parent implements unrealize before chaining up Needed because ClutterGroup no longer has to implement unrealize. * Perform unrealize in the default handler for the signal. This allows non-containers that have children to work properly, and allows containers to override how it's done. * Add map/unmap virtual methods and set MAPPED flag on self and children in there. This allows subclasses to hook map/unmap. These are not signals, because notify::mapped is better for anything it's legitimate for a non-subclass to do. Signed-off-by: Emmanuele Bassi <ebassi@linux.intel.com> --- clutter/clutter-actor.c | 819 +++++++++++++++++++++++--- clutter/clutter-actor.h | 12 +- clutter/clutter-group.c | 18 - clutter/clutter-private.h | 4 + clutter/clutter-stage.c | 56 +- clutter/clutter-texture.c | 81 ++- clutter/eglnative/clutter-stage-egl.c | 5 +- clutter/eglx/clutter-stage-egl.c | 6 +- clutter/fruity/clutter-fruity.c | 4 + clutter/fruity/clutter-stage-fruity.c | 5 +- clutter/glx/clutter-stage-glx.c | 12 +- clutter/sdl/clutter-stage-sdl.c | 2 - clutter/win32/clutter-stage-win32.c | 15 +- clutter/x11/clutter-stage-x11.c | 69 ++- doc/clutter-actor-invariants.txt | 180 ++++-- tests/conform/test-actor-invariants.c | 133 ++++- tests/conform/test-conform-main.c | 3 + 17 files changed, 1145 insertions(+), 279 deletions(-) diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c index 5d9d95d6a..3c9890a5f 100644 --- a/clutter/clutter-actor.c +++ b/clutter/clutter-actor.c @@ -136,6 +136,14 @@ * * Evaluates to %TRUE if the %CLUTTER_ACTOR_MAPPED flag is set. * + * Means "the actor will be painted if the stage is mapped." + * + * %TRUE if the actor is visible; and all parents with possible exception + * of the stage are visible; and an ancestor of the actor is a toplevel. + * + * Clutter auto-maintains the mapped flag whenever actors are + * reparented or shown/hidden. + * * Since: 0.2 */ @@ -145,6 +153,15 @@ * * Evaluates to %TRUE if the %CLUTTER_ACTOR_REALIZED flag is set. * + * Whether GL resources such as textures are allocated; + * if an actor is mapped it must also be realized, but an actor + * can be realized and unmapped (this is so hiding an actor temporarily + * doesn't do an expensive unrealize/realize). + * + * To be realized an actor must be inside a stage, and all its parents + * must be realized. The stage is required so the actor knows the + * correct GL context and window system resources to use. + * * Since: 0.2 */ @@ -152,7 +169,12 @@ * CLUTTER_ACTOR_IS_VISIBLE: * @e: a #ClutterActor * - * Evaluates to %TRUE if the actor is both realized and mapped. + * Evaluates to %TRUE if the actor has been shown, %FALSE if it's hidden. + * Equivalent to the ClutterActor::visible object property. + * + * Note that an actor is only painted onscreen if it's mapped, which + * means it's visible, and all its parents are visible, and one of the + * parents is a toplevel stage. * * Since: 0.2 */ @@ -210,6 +232,23 @@ struct _AnchorCoord } v; }; +/* Internal enum used to control mapped state update. This is a hint + * which indicates when to do something other than just enforce + * invariants. + */ +typedef enum { + MAP_STATE_CHECK, /* just enforce invariants. */ + MAP_STATE_MAKE_UNREALIZED, /* force unrealize, ignoring invariants, + * used when about to unparent. + */ + MAP_STATE_MAKE_MAPPED, /* set mapped, error if invariants not met; + * used to set mapped on toplevels. + */ + MAP_STATE_MAKE_UNMAPPED /* set unmapped, even if parent is mapped, + * used just before unmapping parent. + */ +} MapStateChange; + struct _ClutterActorPrivate { /* fixed_x, fixed_y, and the allocation box are all in parent @@ -343,6 +382,7 @@ enum PROP_OPACITY, PROP_VISIBLE, + PROP_MAPPED, PROP_REACTIVE, PROP_SCALE_X, @@ -430,6 +470,9 @@ static void clutter_actor_set_natural_height_set (ClutterActor *self, gboolean use_natural_height); static void clutter_actor_set_request_mode (ClutterActor *self, ClutterRequestMode mode); +static void clutter_actor_update_map_state (ClutterActor *self, + MapStateChange change); +static void clutter_actor_unrealize_not_hiding (ClutterActor *self); /* Helper routines for managing anchor coords */ static void clutter_anchor_coord_get_units (ClutterActor *self, @@ -471,22 +514,404 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (ClutterActor, clutter_scriptable_iface_init)); +/* FIXME this is for debugging only, remove once working (or leave in + * only in some debug mode). Should leave it for a little while + * until we're confident in the new map/realize/visible handling. + */ +static void +clutter_actor_verify_map_state (ClutterActor *self) +{ + if (CLUTTER_ACTOR_IS_REALIZED (self)) + { + /* all bets are off during reparent when we're potentially realized, + * but should not be according to invariants + */ + if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT)) + { + if (self->priv->parent_actor == NULL) + { + if (CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL) + { + } + else + { + g_warning ("Realized non-toplevel actor should have a parent"); + } + } + else if (!CLUTTER_ACTOR_IS_REALIZED (self->priv->parent_actor)) + { + g_warning ("Realized actor %s %p has an unrealized parent %s %p", + G_OBJECT_TYPE_NAME (self), self, + G_OBJECT_TYPE_NAME (self->priv->parent_actor), + self->priv->parent_actor); + } + } + } + + if (CLUTTER_ACTOR_IS_MAPPED (self)) + { + if (!CLUTTER_ACTOR_IS_REALIZED (self)) + g_warning ("Actor is mapped but not realized"); + + /* remaining bets are off during reparent when we're potentially + * mapped, but should not be according to invariants + */ + if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT)) + { + if (self->priv->parent_actor == NULL) + { + if (CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL) + { + if (!CLUTTER_ACTOR_IS_VISIBLE (self)) + g_warning ("Toplevel actor is mapped but not visible"); + } + else + { + g_warning ("Mapped actor %s %p should have a parent", + G_OBJECT_TYPE_NAME (self), self); + } + } + else + { + if (!CLUTTER_ACTOR_IS_VISIBLE (self->priv->parent_actor)) + { + g_warning ("Actor should not be mapped if parent is not visible"); + } + + if (!CLUTTER_ACTOR_IS_REALIZED (self->priv->parent_actor)) + { + g_warning ("Actor should not be mapped if parent is not realized"); + } + + if (!(CLUTTER_PRIVATE_FLAGS (self->priv->parent_actor) & + CLUTTER_ACTOR_IS_TOPLEVEL)) + { + if (!CLUTTER_ACTOR_IS_MAPPED (self->priv->parent_actor)) + g_warning ("Actor is mapped but its non-toplevel parent is not mapped"); + } + } + } + } +} + +static void +clutter_actor_set_mapped (ClutterActor *self, + gboolean mapped) +{ + if (CLUTTER_ACTOR_IS_MAPPED (self) == mapped) + return; + + if (mapped) + { + CLUTTER_ACTOR_GET_CLASS (self)->map (self); + g_assert (CLUTTER_ACTOR_IS_MAPPED (self)); + } + else + { + CLUTTER_ACTOR_GET_CLASS (self)->unmap (self); + g_assert (!CLUTTER_ACTOR_IS_MAPPED (self)); + } +} + +/* this function updates the mapped and realized states according to + * invariants, in the appropriate order. + */ +static void +clutter_actor_update_map_state (ClutterActor *self, + MapStateChange change) +{ + gboolean was_mapped; + gboolean was_realized; + + was_mapped = CLUTTER_ACTOR_IS_MAPPED (self); + was_realized = CLUTTER_ACTOR_IS_REALIZED (self); + + if (CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL) + { + /* the mapped flag on top-level actors must be set by the + * per-backend implementation because it might be asynchronous. + * + * That is, the MAPPED flag on toplevels currently tracks the X + * server mapped-ness of the window, while the expected behavior + * (if used to GTK) may be to track WM_STATE!=WithdrawnState. + * This creates some weird complexity by breaking the invariant + * that if we're visible and all ancestors shown then we are + * also mapped - instead, we are mapped if all ancestors + * _possibly excepting_ the stage are mapped. The stage + * will map/unmap for example when it is minimized or + * moved to another workspace. + * + * So, the only invariant on the stage is that if visible it + * should be realized, and that it has to be visible to be + * mapped. + */ + if (CLUTTER_ACTOR_IS_VISIBLE (self)) + clutter_actor_realize (self); + + switch (change) + { + case MAP_STATE_CHECK: + break; + case MAP_STATE_MAKE_MAPPED: + g_assert (!was_mapped); + clutter_actor_set_mapped (self, TRUE); + break; + case MAP_STATE_MAKE_UNMAPPED: + g_assert (was_mapped); + clutter_actor_set_mapped (self, FALSE); + break; + case MAP_STATE_MAKE_UNREALIZED: + /* we only use MAKE_UNREALIZED in unparent, + * and unparenting a stage isn't possible. + * If someone wants to just unrealize a stage + * then clutter_actor_unrealize() doesn't + * go through this codepath. + */ + g_warning ("Trying to force unrealize stage is not allowed"); + break; + } + + if (CLUTTER_ACTOR_IS_MAPPED (self) && + !CLUTTER_ACTOR_IS_VISIBLE (self)) + g_warning ("Clutter toplevel is not visible, but is somehow still mapped"); + } + else + { + ClutterActorPrivate *priv; + gboolean should_be_mapped; + gboolean may_be_realized; + gboolean must_be_realized; + ClutterActor *parent; + + priv = self->priv; + + should_be_mapped = FALSE; + may_be_realized = TRUE; + must_be_realized = FALSE; + + parent = priv->parent_actor; + + if (parent == NULL || + change == MAP_STATE_MAKE_UNREALIZED) + { + may_be_realized = FALSE; + } + else + { + /* Maintain invariant that if parent is mapped, and we are + * visible, then we are mapped ... unless parent is a + * stage, in which case we map regardless of parent's map + * state but do require stage to be visible and realized. + * + * If parent is realized, that does not force us to be + * realized; but if parent is unrealized, that does force + * us to be unrealized. + * + * The reason we don't force children to realize with + * parents is _clutter_actor_rerealize(); if we require that + * a realized parent means children are realized, then to + * unrealize an actor we would have to unrealize its + * parents, which would end up meaning unrealizing and + * hiding the entire stage. So we allow unrealizing a + * child (as long as that child is not mapped) while that + * child still has a realized parent. + * + * Also, if we unrealize from leaf nodes to root, and + * realize from root to leaf, the invariants are never + * violated if we allow children to be unrealized + * while parents are realized. + * + * When unmapping, MAP_STATE_MAKE_UNMAPPED is specified + * to force us to unmap, even though parent is still + * mapped. This is because we're unmapping from leaf nodes + * up to root nodes. + */ + if (CLUTTER_ACTOR_IS_VISIBLE (self) && + change != MAP_STATE_MAKE_UNMAPPED) + { + gboolean parent_is_visible_realized_toplevel; + + parent_is_visible_realized_toplevel = + (((CLUTTER_PRIVATE_FLAGS (parent) & + CLUTTER_ACTOR_IS_TOPLEVEL) != 0) && + CLUTTER_ACTOR_IS_VISIBLE (parent) && + CLUTTER_ACTOR_IS_REALIZED (parent)); + + if (CLUTTER_ACTOR_IS_MAPPED (parent) || + parent_is_visible_realized_toplevel) + { + must_be_realized = TRUE; + should_be_mapped = TRUE; + } + } + + if (!CLUTTER_ACTOR_IS_REALIZED (parent)) + may_be_realized = FALSE; + } + + if (change == MAP_STATE_MAKE_MAPPED && !should_be_mapped) + { + g_warning ("Attempting to map a child that does not meet the necessary invariants"); + } + + /* If in reparent, we temporarily suspend unmap and unrealize. + * + * We want to go in the order "realize, map" and "unmap, unrealize" + */ + + /* Unmap */ + if (!should_be_mapped && + !(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT)) + { + clutter_actor_set_mapped (self, FALSE); + } + + /* Realize */ + if (must_be_realized) + clutter_actor_realize (self); + + /* if we must be realized then we may be, presumably */ + g_assert (!(must_be_realized && !may_be_realized)); + + /* Unrealize */ + if (!may_be_realized && + !(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT)) + clutter_actor_unrealize_not_hiding (self); + + /* Map */ + if (should_be_mapped) + { + if (!must_be_realized) + g_warning ("Somehow we think an actor should be mapped but not realized, which isn't allowed"); + + /* realization is allowed to fail (though I don't know what + * an app is supposed to do about that - shouldn't it just + * be a g_error? anyway, we have to avoid mapping if this + * happens) + */ + if (CLUTTER_ACTOR_IS_REALIZED (self)) + { + clutter_actor_set_mapped (self, TRUE); + } + } + } + + /* check all invariants were kept */ + clutter_actor_verify_map_state (self); +} + +static void +clutter_actor_real_map (ClutterActor *self) +{ + g_assert (!CLUTTER_ACTOR_IS_MAPPED (self)); + + CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_MAPPED); + /* notify on parent mapped before potentially mapping + * children, so apps see a top-down notification. + */ + g_object_notify (G_OBJECT (self), "mapped"); + + clutter_actor_queue_redraw (self); + + if (CLUTTER_IS_CONTAINER (self)) + clutter_container_foreach (CLUTTER_CONTAINER (self), + CLUTTER_CALLBACK (clutter_actor_map), + NULL); +} + +/** + * clutter_actor_map: + * @self: A #ClutterActor + * + * Sets the #CLUTTER_ACTOR_MAPPED flag on the actor + * and possibly maps and realizes its children + * if they are visible. Does nothing if the + * actor is not visible. + * + * Calling this is allowed in only one case: + * you are implementing the "map" virtual function + * in an actor and you need to map the children of + * that actor. It is not necessary to call this + * if you implement #ClutterContainer because the + * default implementation will automatically map + * children of containers. + * + * When overriding map, it is mandatory to chain up to the parent + * implementation. + **/ +void +clutter_actor_map (ClutterActor *self) +{ + g_return_if_fail (CLUTTER_IS_ACTOR (self)); + + if (CLUTTER_ACTOR_IS_MAPPED (self)) + return; + + if (!CLUTTER_ACTOR_IS_VISIBLE (self)) + return; + + clutter_actor_update_map_state (self, MAP_STATE_MAKE_MAPPED); +} + +static void +clutter_actor_real_unmap (ClutterActor *self) +{ + g_assert (CLUTTER_ACTOR_IS_MAPPED (self)); + + if (CLUTTER_IS_CONTAINER (self)) + clutter_container_foreach (CLUTTER_CONTAINER (self), + CLUTTER_CALLBACK (clutter_actor_unmap), + NULL); + + CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_MAPPED); + /* notify on parent mapped after potentially unmapping + * children, so apps see a bottom-up notification. + */ + g_object_notify (G_OBJECT (self), "mapped"); + + clutter_actor_queue_redraw (self); +} + +/** + * clutter_actor_unmap: + * @self: A #ClutterActor + * + * Unsets the #CLUTTER_ACTOR_MAPPED flag on the actor and possibly + * unmaps its children if they were mapped. + * + * Calling this is allowed in only one case: + * you are implementing the "unmap" virtual function + * in an actor and you need to unmap the children of + * that actor. It is not necessary to call this + * if you implement #ClutterContainer because the + * default implementation will automatically unmap + * children of containers. + * + * When overriding unmap, it is mandatory to chain up to the parent + * implementation. + **/ +void +clutter_actor_unmap (ClutterActor *self) +{ + g_return_if_fail (CLUTTER_IS_ACTOR (self)); + + if (!CLUTTER_ACTOR_IS_MAPPED (self)) + return; + + clutter_actor_update_map_state (self, MAP_STATE_MAKE_UNMAPPED); +} + static void clutter_actor_real_show (ClutterActor *self) { if (!CLUTTER_ACTOR_IS_VISIBLE (self)) { - if (!CLUTTER_ACTOR_IS_REALIZED (self)) - clutter_actor_realize (self); - - /* the mapped flag on the top-level actors must be set by the - * per-backend implementation because it might be asynchronous + CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_VISIBLE); + /* we notify on the "visible" flag in the clutter_actor_show() + * wrapper so the entire show signal emission completes first + * (?) */ - if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL)) - CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_MAPPED); - - if (CLUTTER_ACTOR_IS_VISIBLE (self)) - clutter_actor_queue_redraw (self); + clutter_actor_update_map_state (self, MAP_STATE_CHECK); clutter_actor_queue_relayout (self); } @@ -512,6 +937,8 @@ clutter_actor_show (ClutterActor *self) g_return_if_fail (CLUTTER_IS_ACTOR (self)); + clutter_actor_verify_map_state (self); /* FIXME leave this for debugging only */ + priv = self->priv; g_object_freeze_notify (G_OBJECT (self)); @@ -556,11 +983,12 @@ clutter_actor_real_hide (ClutterActor *self) { if (CLUTTER_ACTOR_IS_VISIBLE (self)) { - /* see comment in clutter_actor_real_show() on why we don't set - * the mapped flag on the top-level actors + CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_VISIBLE); + /* we notify on the "visible" flag in the clutter_actor_hide() + * wrapper so the entire hide signal emission completes first + * (?) */ - if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL)) - CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_MAPPED); + clutter_actor_update_map_state (self, MAP_STATE_CHECK); clutter_actor_queue_relayout (self); } @@ -586,6 +1014,8 @@ clutter_actor_hide (ClutterActor *self) g_return_if_fail (CLUTTER_IS_ACTOR (self)); + clutter_actor_verify_map_state (self); /* FIXME leave this for debugging only */ + priv = self->priv; g_object_freeze_notify (G_OBJECT (self)); @@ -596,7 +1026,7 @@ clutter_actor_hide (ClutterActor *self) g_object_notify (G_OBJECT (self), "show-on-set-parent"); } - if (CLUTTER_ACTOR_IS_MAPPED (self)) + if (CLUTTER_ACTOR_IS_VISIBLE (self)) { g_signal_emit (self, actor_signals[HIDE], 0); g_object_notify (G_OBJECT (self), "visible"); @@ -631,44 +1061,225 @@ clutter_actor_hide_all (ClutterActor *self) * * Creates any underlying graphics resources needed by the actor to be * displayed. + * + * Realization means the actor is now tied to a specific rendering context + * (that is, a specific toplevel stage). + * + * This function does nothing if the actor is already realized. + * + * Because a realized actor must have realized parent actors, calling + * clutter_actor_realize() will also realize all parents of the actor. + * + * This function does not realize child actors, except in the special + * case that realizing the stage, when the stage is visible, will + * suddenly map (and thus realize) the children of the stage. **/ void clutter_actor_realize (ClutterActor *self) { g_return_if_fail (CLUTTER_IS_ACTOR (self)); + clutter_actor_verify_map_state (self); /* FIXME leave this for debugging only */ + if (CLUTTER_ACTOR_IS_REALIZED (self)) return; + /* To be realized, our parent actors must be realized first. + * This will only succeed if we're inside a toplevel. + */ + if (self->priv->parent_actor) + clutter_actor_realize (self->priv->parent_actor); + + if (CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL) + { + /* toplevels can be realized at any time */ + } + else + { + /* "Fail" the realization if parent is missing or unrealized; + * this should really be a g_warning() not some kind of runtime + * failure; how can an app possibly recover? Instead it's a bug + * in the app and the app should get an explanatory warning so + * someone can fix it. But for now it's too hard to fix this + * because e.g. ClutterTexture needs reworking. + */ + if (self->priv->parent_actor == NULL || + !CLUTTER_ACTOR_IS_REALIZED (self->priv->parent_actor)) + return; + } + CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_REALIZED); g_signal_emit (self, actor_signals[REALIZE], 0); + + /* Stage actor is allowed to unset the realized flag again in its + * default signal handler, though that is a pathological situation. + */ + + /* If realization "failed" we'll have to update child state. */ + clutter_actor_update_map_state (self, MAP_STATE_CHECK); +} + +void +clutter_actor_real_unrealize (ClutterActor *self) +{ + /* we must be unmapped (implying our children are also unmapped) */ + g_assert (!CLUTTER_ACTOR_IS_MAPPED (self)); + + if (CLUTTER_IS_CONTAINER (self)) + clutter_container_foreach (CLUTTER_CONTAINER (self), + CLUTTER_CALLBACK (clutter_actor_unrealize_not_hiding), + NULL); } /** * clutter_actor_unrealize: * @self: A #ClutterActor * - * Frees up any underlying graphics resources needed by the actor to be - * displayed. - **/ + * Frees up any underlying graphics resources needed by the actor to + * be displayed. + * + * Unrealization means the actor is now independent of any specific + * rendering context (is not attached to a specific toplevel stage). + * + * Because mapped actors must be realized, actors may not be + * unrealized if they are mapped. This function hides the actor to be + * sure it isn't mapped, an application-visible side effect that you + * may not be expecting. + * + * This function should not really be in the public API, because + * there isn't a good reason to call it. ClutterActor will already + * unrealize things for you when it's important to do so. + * + * If you were using clutter_actor_unrealize() in a dispose + * implementation, then don't, just chain up to ClutterActor's + * dispose. + * + * If you were using clutter_actor_unrealize() to implement + * unrealizing children of your container, then don't, ClutterActor + * will already take care of that. + * + * If you were using clutter_actor_unrealize() to re-realize to + * create your resources in a different way, then use + * _clutter_actor_rerealize() (inside Clutter) or just call your + * code that recreates your resources directly (outside Clutter). + */ void clutter_actor_unrealize (ClutterActor *self) { g_return_if_fail (CLUTTER_IS_ACTOR (self)); + g_return_if_fail (!CLUTTER_ACTOR_IS_MAPPED (self)); + + clutter_actor_verify_map_state (self); /* FIXME leave this for debugging only */ + + clutter_actor_hide (self); + + clutter_actor_unrealize_not_hiding (self); +} + +/** + * clutter_actor_unrealize_not_hiding: + * @self: A #ClutterActor + * + * Frees up any underlying graphics resources needed by the actor to + * be displayed. + * + * Unrealization means the actor is now independent of any specific + * rendering context (is not attached to a specific toplevel stage). + * + * Because mapped actors must be realized, actors may not be + * unrealized if they are mapped. You must hide the actor or one of + * its parents before attempting to unrealize. + * + * This function is separate from clutter_actor_unrealize() because it + * does not automatically hide the actor. + * Actors need not be hidden to be unrealized, they just need to + * be unmapped. In fact we don't want to mess up the application's + * setting of the "visible" flag, so hiding is very undesirable. + * + * clutter_actor_unrealize() does a clutter_actor_hide() just for + * backward compatibility. + */ +static void +clutter_actor_unrealize_not_hiding (ClutterActor *self) +{ + /* All callers of clutter_actor_unrealize_not_hiding() should have + * taken care of unmapping the actor first. This means + * all our children should also be unmapped. + */ + g_assert (!CLUTTER_ACTOR_IS_MAPPED (self)); if (!CLUTTER_ACTOR_IS_REALIZED (self)) return; - /* unrealizing also means hiding a visible actor, exactly - * like showing implies realization if called on an unrealized - * actor. this keeps the flags in sync. + /* The default handler for the signal should recursively unrealize + * child actors. We want to unset the realized flag only _after_ + * child actors are unrealized, to maintain invariants. */ - clutter_actor_hide (self); - - CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_REALIZED); g_signal_emit (self, actor_signals[UNREALIZE], 0); + + CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_REALIZED); +} + +/** + * _clutter_actor_rerealize: + * @self: A #ClutterActor + * @callback: Function to call while unrealized + * @data: data for callback + * + * If an actor is already unrealized, this just calls the callback. + * + * If it is realized, it unrealizes temporarily, calls the callback, + * and then re-realizes the actor. + * + * As a side effect, leaves all children of the actor unrealized if + * the actor was realized but not showing. This is because when we + * unrealize the actor temporarily we must unrealize its children + * (e.g. children of a stage can't be realized if stage window is + * gone). And we aren't clever enough to save the realization state of + * all children. In most cases this should not matter, because + * the children will automatically realize when they next become mapped. + */ +void +_clutter_actor_rerealize (ClutterActor *self, + ClutterCallback callback, + void *data) +{ + gboolean was_mapped; + gboolean was_showing; + gboolean was_realized; + + g_return_if_fail (CLUTTER_IS_ACTOR (self)); + + clutter_actor_verify_map_state (self); /* FIXME leave this for debugging only */ + + was_realized = CLUTTER_ACTOR_IS_REALIZED (self); + was_mapped = CLUTTER_ACTOR_IS_MAPPED (self); + was_showing = CLUTTER_ACTOR_IS_VISIBLE (self); + + /* Must be unmapped to unrealize. Note we only have to hide this + * actor if it was mapped (if all parents were showing). If actor + * is merely visible (but not mapped), then that's fine, we can + * leave it visible. + */ + if (was_mapped) + clutter_actor_hide (self); + + g_assert (!CLUTTER_ACTOR_IS_MAPPED (self)); + + /* unrealize self and all children */ + clutter_actor_unrealize_not_hiding (self); + + if (callback != NULL) + { + (* callback) (self, data); + } + + if (was_showing) + clutter_actor_show (self); /* will realize only if mapping implies it */ + else if (was_realized) + clutter_actor_realize (self); /* realize self and all parents */ } static void @@ -892,7 +1503,7 @@ clutter_actor_queue_redraw_with_origin (ClutterActor *self, ClutterActor *origin) { /* short-circuit the trivial case */ - if (!CLUTTER_ACTOR_IS_VISIBLE(self)) + if (!CLUTTER_ACTOR_IS_MAPPED(self)) return; /* already queued since last paint() */ @@ -910,7 +1521,7 @@ clutter_actor_real_queue_redraw (ClutterActor *self, ClutterActor *parent; /* short-circuit the trivial case */ - if (!CLUTTER_ACTOR_IS_VISIBLE (self)) + if (!CLUTTER_ACTOR_IS_MAPPED (self)) return; /* already queued since last paint() */ @@ -1562,17 +2173,11 @@ clutter_actor_paint (ClutterActor *self) priv = self->priv; - if (!CLUTTER_ACTOR_IS_REALIZED (self)) - { - CLUTTER_NOTE (PAINT, "Attempting realize via paint()"); - clutter_actor_realize(self); - - if (!CLUTTER_ACTOR_IS_REALIZED (self)) - { - CLUTTER_NOTE (PAINT, "Attempt failed, aborting paint"); - return; - } - } + /* if we aren't paintable (not in a toplevel with all + * parents paintable) then do nothing. + */ + if (!CLUTTER_ACTOR_IS_MAPPED (self)) + return; /* mark that we are in the paint process */ CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_IN_PAINT); @@ -1985,7 +2590,11 @@ clutter_actor_get_property (GObject *object, break; case PROP_VISIBLE: g_value_set_boolean (value, - (CLUTTER_ACTOR_IS_VISIBLE (actor) != FALSE)); + CLUTTER_ACTOR_IS_VISIBLE (actor)); + break; + case PROP_MAPPED: + g_value_set_boolean (value, + CLUTTER_ACTOR_IS_MAPPED (actor)); break; case PROP_HAS_CLIP: g_value_set_boolean (value, priv->has_clip); @@ -2123,7 +2732,11 @@ clutter_actor_dispose (GObject *object) priv->parent_actor = NULL; } - clutter_actor_unrealize (self); + /* parent should be gone */ + g_assert (priv->parent_actor == NULL); + /* can't be mapped or realized with no parent */ + g_assert (!CLUTTER_ACTOR_IS_MAPPED (self)); + g_assert (!CLUTTER_ACTOR_IS_REALIZED (self)); destroy_shader_data (self); @@ -2527,6 +3140,19 @@ clutter_actor_class_init (ClutterActorClass *klass) "Whether the actor is visible or not", FALSE, CLUTTER_PARAM_READWRITE)); + + /** + * ClutterActor:mapped: + * + * Whether the actor is mapped (will be painted when stage is mapped). + */ + g_object_class_install_property (object_class, + PROP_MAPPED, + g_param_spec_boolean ("mapped", + "Mapped", + "Whether the actor will be painted", + FALSE, + G_PARAM_READABLE)); /** * ClutterActor:reactive: * @@ -3321,6 +3947,30 @@ clutter_actor_class_init (ClutterActorClass *klass) clutter_marshal_VOID__VOID, G_TYPE_NONE, 0); + /** + * ClutterActor::map: + * @actor: the #ClutterActor to map + * + * The ::map virtual functon must be overridden in order to call + * clutter_actor_map() on any child actors if the actor is not a + * #ClutterContainer. When overriding, it is mandatory to chain up + * to the parent implementation. + * + * Since: 1.0 + */ + + /** + * ClutterActor::unmap: + * @actor: the #ClutterActor to unmap + * + * The ::unmap virtual functon must be overridden in order to call + * clutter_actor_unmap() on any child actors if the actor is not a + * #ClutterContainer. When overriding, it is mandatory to chain up + * to the parent implementation. + * + * Since: 1.0 + */ + /** * ClutterActor::pick: * @actor: the #ClutterActor that received the signal @@ -3353,6 +4003,9 @@ clutter_actor_class_init (ClutterActorClass *klass) klass->show_all = clutter_actor_show; klass->hide = clutter_actor_real_hide; klass->hide_all = clutter_actor_hide; + klass->map = clutter_actor_real_map; + klass->unmap = clutter_actor_real_unmap; + klass->unrealize = clutter_actor_real_unrealize; klass->pick = clutter_actor_real_pick; klass->get_preferred_width = clutter_actor_real_get_preferred_width; klass->get_preferred_height = clutter_actor_real_get_preferred_height; @@ -3473,9 +4126,8 @@ clutter_actor_queue_relayout (ClutterActor *self) priv->needs_height_request = TRUE; priv->needs_allocation = TRUE; - /* always repaint also */ - if (CLUTTER_ACTOR_IS_VISIBLE (self)) - clutter_actor_queue_redraw (self); + /* always repaint also (no-op if not mapped) */ + clutter_actor_queue_redraw (self); /* We need to go all the way up the hierarchy */ if (priv->parent_actor) @@ -6225,19 +6877,15 @@ clutter_actor_set_parent (ClutterActor *self, if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT)) g_signal_emit (self, actor_signals[PARENT_SET], 0, NULL); - /* the invariant is: if the parent is realized, the we must be - * realized after set_parent(). the call to clutter_actor_show() - * will cause this anyway, but we need to maintain the invariant - * even for actors that have :show-on-set-parent set to FALSE + /* If parent is mapped or realized, we need to also be mapped or + * realized once we're inside the parent. */ - if (CLUTTER_ACTOR_IS_REALIZED (priv->parent_actor)) - clutter_actor_realize (self); + clutter_actor_update_map_state (self, MAP_STATE_CHECK); if (priv->show_on_set_parent) clutter_actor_show (self); - if (CLUTTER_ACTOR_IS_VISIBLE (priv->parent_actor) && - CLUTTER_ACTOR_IS_VISIBLE (self)) + if (CLUTTER_ACTOR_IS_MAPPED (self)) { clutter_actor_queue_redraw (self); } @@ -6284,6 +6932,8 @@ clutter_actor_get_parent (ClutterActor *self) * Retrieves the 'paint' visibility of an actor recursively checking for non * visible parents. * + * This is by definition the same as CLUTTER_ACTOR_IS_MAPPED(). + * * Return Value: TRUE if the actor is visibile and will be painted. * * Since: 0.8.4 @@ -6293,17 +6943,7 @@ clutter_actor_get_paint_visibility (ClutterActor *actor) { g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), FALSE); - do - { - if (!CLUTTER_ACTOR_IS_VISIBLE (actor)) - return FALSE; - - if (CLUTTER_PRIVATE_FLAGS (actor) & CLUTTER_ACTOR_IS_TOPLEVEL) - return TRUE; - } - while ((actor = clutter_actor_get_parent (actor)) != NULL); - - return FALSE; + return CLUTTER_ACTOR_IS_MAPPED (actor); } /** @@ -6323,8 +6963,7 @@ clutter_actor_unparent (ClutterActor *self) { ClutterActorPrivate *priv; ClutterActor *old_parent; - - gboolean show_on_set_parent_enabled = TRUE; + gboolean was_mapped; g_return_if_fail (CLUTTER_IS_ACTOR (self)); @@ -6333,47 +6972,29 @@ clutter_actor_unparent (ClutterActor *self) if (priv->parent_actor == NULL) return; - show_on_set_parent_enabled = priv->show_on_set_parent; + was_mapped = CLUTTER_ACTOR_IS_MAPPED (self); + + /* we need to unrealize *before* we set parent_actor to NULL, + * because in an unrealize method actors are dissociating from the + * stage, which means they need to be able to + * clutter_actor_get_stage(). This should unmap and unrealize, + * unless we're reparenting. + */ + clutter_actor_update_map_state (self, MAP_STATE_MAKE_UNREALIZED); old_parent = priv->parent_actor; priv->parent_actor = NULL; - /* if we are uparenting we hide ourselves; if we are just reparenting - * there's no need to do that, as the paint is fast enough. - */ - if (CLUTTER_ACTOR_IS_REALIZED (self)) - { - if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT)) - clutter_actor_hide (self); - } - - /* clutter_actor_hide() will set the :show-on-set-parent property - * to FALSE because the actor doesn't have a parent anymore; but - * we need to return the actor to its initial state, so we force - * the state of the :show-on-set-parent property to its value - * previous the unparenting - */ - priv->show_on_set_parent = show_on_set_parent_enabled; - - if (CLUTTER_ACTOR_IS_VISIBLE (self)) - clutter_actor_queue_redraw (self); - /* clutter_actor_reparent() will emit ::parent-set for us */ if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT)) g_signal_emit (self, actor_signals[PARENT_SET], 0, old_parent); - /* Queue a redraw on old_parent */ - if (CLUTTER_ACTOR_IS_VISIBLE (old_parent)) + /* Queue a redraw on old_parent only if we were painted in the first + * place. Will be no-op if old parent is not shown. + */ + if (was_mapped && !CLUTTER_ACTOR_IS_MAPPED (self)) clutter_actor_queue_redraw (old_parent); - /* Could also need to relayout */ - if (old_parent->priv->needs_width_request || - old_parent->priv->needs_height_request || - old_parent->priv->needs_allocation) - { - clutter_actor_queue_relayout (old_parent); - } - /* remove the reference we acquired in clutter_actor_set_parent() */ g_object_unref (self); } @@ -6385,7 +7006,10 @@ clutter_actor_unparent (ClutterActor *self) * * This function resets the parent actor of @self. It is * logically equivalent to calling clutter_actor_unparent() - * and clutter_actor_set_parent(). + * and clutter_actor_set_parent(), but more efficiently + * implemented, ensures the child is not finalized + * when unparented, and emits the parent-set signal only + * one time. * * Since: 0.2 */ @@ -6438,6 +7062,9 @@ clutter_actor_reparent (ClutterActor *self, g_object_unref (self); CLUTTER_UNSET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_IN_REPARENT); + + /* the IN_REPARENT flag suspends state updates */ + clutter_actor_update_map_state (self, MAP_STATE_CHECK); } } /** diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h index 6ff80c3e5..c4941ecdd 100644 --- a/clutter/clutter-actor.h +++ b/clutter/clutter-actor.h @@ -76,7 +76,7 @@ G_BEGIN_DECLS #define CLUTTER_ACTOR_IS_MAPPED(e) ((((ClutterActor*)(e))->flags & CLUTTER_ACTOR_MAPPED) != FALSE) #define CLUTTER_ACTOR_IS_REALIZED(e) ((((ClutterActor*)(e))->flags & CLUTTER_ACTOR_REALIZED) != FALSE) -#define CLUTTER_ACTOR_IS_VISIBLE(e) (CLUTTER_ACTOR_IS_MAPPED (e) && CLUTTER_ACTOR_IS_REALIZED (e)) +#define CLUTTER_ACTOR_IS_VISIBLE(e) ((((ClutterActor*)(e))->flags & CLUTTER_ACTOR_VISIBLE) != FALSE) #define CLUTTER_ACTOR_IS_REACTIVE(e) ((((ClutterActor*)(e))->flags & CLUTTER_ACTOR_REACTIVE) != FALSE) typedef struct _ClutterActorClass ClutterActorClass; @@ -102,11 +102,12 @@ typedef void (*ClutterCallback) (ClutterActor *actor, gpointer data); /** * ClutterActorFlags: - * @CLUTTER_ACTOR_MAPPED: the actor has been painted + * @CLUTTER_ACTOR_MAPPED: the actor will be painted (is visible, and inside a toplevel, and all parents visible) * @CLUTTER_ACTOR_REALIZED: the resources associated to the actor have been * allocated * @CLUTTER_ACTOR_REACTIVE: the actor 'reacts' to mouse events emmitting event * signals + * @CLUTTER_ACTOR_VISIBLE: the actor has been shown by the application program * * Flags used to signal the state of an actor. */ @@ -114,7 +115,8 @@ typedef enum { CLUTTER_ACTOR_MAPPED = 1 << 1, CLUTTER_ACTOR_REALIZED = 1 << 2, - CLUTTER_ACTOR_REACTIVE = 1 << 3 + CLUTTER_ACTOR_REACTIVE = 1 << 3, + CLUTTER_ACTOR_VISIBLE = 1 << 4 } ClutterActorFlags; /** @@ -220,6 +222,8 @@ struct _ClutterActorClass void (* hide_all) (ClutterActor *actor); void (* realize) (ClutterActor *actor); void (* unrealize) (ClutterActor *actor); + void (* map) (ClutterActor *actor); + void (* unmap) (ClutterActor *actor); void (* paint) (ClutterActor *actor); void (* parent_set) (ClutterActor *actor, ClutterActor *old_parent); @@ -280,6 +284,8 @@ void clutter_actor_hide (ClutterActor void clutter_actor_hide_all (ClutterActor *self); void clutter_actor_realize (ClutterActor *self); void clutter_actor_unrealize (ClutterActor *self); +void clutter_actor_map (ClutterActor *self); +void clutter_actor_unmap (ClutterActor *self); void clutter_actor_paint (ClutterActor *self); void clutter_actor_pick (ClutterActor *self, const ClutterColor *color); diff --git a/clutter/clutter-group.c b/clutter/clutter-group.c index 712acb464..a6a15d4ee 100644 --- a/clutter/clutter-group.c +++ b/clutter/clutter-group.c @@ -105,22 +105,6 @@ clutter_group_paint (ClutterActor *actor) : "unknown"); } -static void -clutter_group_realize (ClutterActor *actor) -{ - clutter_container_foreach (CLUTTER_CONTAINER (actor), - CLUTTER_CALLBACK (clutter_actor_realize), - NULL); -} - -static void -clutter_group_unrealize (ClutterActor *actor) -{ - clutter_container_foreach (CLUTTER_CONTAINER (actor), - CLUTTER_CALLBACK (clutter_actor_unrealize), - NULL); -} - static void clutter_group_pick (ClutterActor *actor, const ClutterColor *color) @@ -587,8 +571,6 @@ clutter_group_class_init (ClutterGroupClass *klass) actor_class->pick = clutter_group_pick; actor_class->show_all = clutter_group_real_show_all; actor_class->hide_all = clutter_group_real_hide_all; - actor_class->realize = clutter_group_realize; - actor_class->unrealize = clutter_group_unrealize; actor_class->get_preferred_width = clutter_group_get_preferred_width; actor_class->get_preferred_height = clutter_group_get_preferred_height; diff --git a/clutter/clutter-private.h b/clutter/clutter-private.h index 912a05c01..f102a0b90 100644 --- a/clutter/clutter-private.h +++ b/clutter/clutter-private.h @@ -217,6 +217,10 @@ gboolean _clutter_boolean_handled_accumulator (GSignalInvocationHint *ihint, void _clutter_actor_apply_modelview_transform_recursive (ClutterActor *self, ClutterActor *ancestor); +void _clutter_actor_rerealize (ClutterActor *self, + ClutterCallback callback, + void *data); + void _clutter_actor_set_opacity_parent (ClutterActor *self, ClutterActor *parent); diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c index 14b9505c3..9e3576264 100644 --- a/clutter/clutter-stage.c +++ b/clutter/clutter-stage.c @@ -263,15 +263,13 @@ clutter_stage_realize (ClutterActor *self) { ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv; - CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_REALIZED); - /* 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_GET_CLASS (priv->impl)->realize (priv->impl); + clutter_actor_realize (priv->impl); /* ensure that the stage is using the context if the * realization sequence was successful @@ -287,12 +285,9 @@ clutter_stage_unrealize (ClutterActor *self) { ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv; - /* unset the flag */ - CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_REALIZED); - /* and then unrealize the implementation */ g_assert (priv->impl != NULL); - CLUTTER_ACTOR_GET_CLASS (priv->impl)->unrealize (priv->impl); + clutter_actor_unrealize (priv->impl); clutter_stage_ensure_current (CLUTTER_STAGE (self)); } @@ -304,9 +299,6 @@ clutter_stage_show (ClutterActor *self) g_assert (priv->impl != NULL); - if (!CLUTTER_ACTOR_IS_REALIZED (priv->impl)) - clutter_actor_realize (priv->impl); - clutter_actor_show (priv->impl); CLUTTER_ACTOR_CLASS (clutter_stage_parent_class)->show (self); @@ -394,6 +386,14 @@ clutter_stage_real_queue_redraw (ClutterActor *actor, } } +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, @@ -415,24 +415,26 @@ clutter_stage_set_property (GObject *object, break; case PROP_OFFSCREEN: - if (priv->is_offscreen == g_value_get_boolean (value)) - return; + { + gboolean was_showing; - if (CLUTTER_ACTOR_IS_REALIZED (actor)) - { - /* Backend needs to check this prop and handle accordingly - * in realise. - * FIXME: More 'obvious' implementation needed? - */ - clutter_actor_unrealize (actor); - priv->is_offscreen = g_value_get_boolean (value); - clutter_actor_realize (actor); + if (priv->is_offscreen == g_value_get_boolean (value)) + return; - if (!CLUTTER_ACTOR_IS_REALIZED (actor)) - priv->is_offscreen = ~g_value_get_boolean (value); - } - else - priv->is_offscreen = g_value_get_boolean (value); + 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: @@ -534,7 +536,7 @@ clutter_stage_dispose (GObject *object) ClutterStagePrivate *priv = stage->priv; ClutterStageManager *stage_manager = clutter_stage_manager_get_default (); - clutter_actor_unrealize (CLUTTER_ACTOR (object)); + clutter_actor_hide (CLUTTER_ACTOR (object)); if (priv->update_idle) { diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index 52dc01c98..50c2e5de0 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -335,19 +335,15 @@ clutter_texture_realize (ClutterActor *actor) } else { - if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_READ_PIXELS)) - { - /* Dont allow realization with no data - note set_data - * will set realize flags. - */ - CLUTTER_NOTE (TEXTURE, - "Texture has no image data cannot realize"); - - CLUTTER_NOTE (TEXTURE, "flags %i", actor->flags); - CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED); - CLUTTER_NOTE (TEXTURE, "flags %i", actor->flags); - return; - } + /* If we have no data, then realization is a no-op but + * we still want to be in REALIZED state to maintain + * invariants. We may have already created the texture + * if someone set some data earlier, or we may create it + * later if someone sets some data later. The fact that + * we may have created it earlier is really a bug, since + * it means ClutterTexture can have GL resources without + * being realized. + */ } CLUTTER_NOTE (TEXTURE, "Texture realized"); @@ -539,9 +535,6 @@ clutter_texture_paint (ClutterActor *self) return; } - if (!CLUTTER_ACTOR_IS_REALIZED (CLUTTER_ACTOR(texture))) - clutter_actor_realize (CLUTTER_ACTOR(texture)); - if (priv->fbo_handle != COGL_INVALID_HANDLE) { ClutterMainContext *context; @@ -1392,6 +1385,12 @@ clutter_texture_get_cogl_texture (ClutterTexture *texture) * @cogl_tex. A reference to the texture is taken so if the handle is * no longer needed it should be deref'd with cogl_handle_unref. * + * This should not be called on an unrealizable texture (one that + * isn't inside a stage). (Currently the ClutterTexture + * implementation relies on being able to have a GL texture while + * unrealized, which means you can get away with it, but it's + * not correct and may change in the future.) + * * Since: 0.8 */ void @@ -1405,6 +1404,12 @@ clutter_texture_set_cogl_texture (ClutterTexture *texture, g_return_if_fail (CLUTTER_IS_TEXTURE (texture)); g_return_if_fail (cogl_is_texture (cogl_tex)); + /* FIXME this implementation should realize the actor if it's in a + * stage, and warn and return if not in a stage yet. However, right + * now everything would break if we did that, so we just fudge it + * and we're broken: we can have a texture without being realized. + */ + priv = texture->priv; width = cogl_texture_get_width (cogl_tex); @@ -1420,6 +1425,14 @@ clutter_texture_set_cogl_texture (ClutterTexture *texture, /* Remove old texture */ texture_free_gl_resources (texture); + + /* Free any saved data so realization doesn't resend it to GL */ + if (priv->local_data) + { + g_free (priv->local_data); + priv->local_data = NULL; + } + /* Use the new texture */ cogl_material_set_layer (priv->material, 0, cogl_tex); @@ -1436,8 +1449,6 @@ clutter_texture_set_cogl_texture (ClutterTexture *texture, priv->width, priv->height); - CLUTTER_ACTOR_SET_FLAGS (CLUTTER_ACTOR (texture), CLUTTER_ACTOR_REALIZED); - if (size_change) { g_signal_emit (texture, texture_signals[SIZE_CHANGE], 0, @@ -1480,6 +1491,10 @@ clutter_texture_set_from_data (ClutterTexture *texture, if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH) flags |= COGL_TEXTURE_AUTO_MIPMAP; + /* FIXME if we are not realized, we should store the data + * for future use, instead of creating the texture. + */ + new_texture = cogl_texture_new_from_data (width, height, max_waste, flags, source_format, @@ -2004,15 +2019,8 @@ clutter_texture_set_filter_quality (ClutterTexture *texture, filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH) && CLUTTER_ACTOR_IS_REALIZED (texture)) { - gboolean was_visible; - - was_visible = CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (texture)); - - clutter_actor_unrealize (CLUTTER_ACTOR (texture)); - clutter_actor_realize (CLUTTER_ACTOR (texture)); - - if (was_visible) - clutter_actor_show (CLUTTER_ACTOR (texture)); + _clutter_actor_rerealize (CLUTTER_ACTOR (texture), + NULL, NULL); } g_object_notify (G_OBJECT (texture), "filter-quality"); @@ -2172,9 +2180,13 @@ clutter_texture_get_base_size (ClutterTexture *texture, /* Attempt to realize, mainly for subclasses ( such as labels ) * which may not create pixbuf data and thus base size until - * realization happens. + * realization happens. If we aren't in a stage we can't realize + * though. Doing this here is probably just broken; instead + * we could virtualize get_base_size, or have the subclasses + * create the pixbufs sooner, or something better. */ - if (!CLUTTER_ACTOR_IS_REALIZED (texture)) + if (!CLUTTER_ACTOR_IS_REALIZED (texture) && + clutter_actor_get_stage (CLUTTER_ACTOR (texture)) != NULL) clutter_actor_realize (CLUTTER_ACTOR (texture)); if (width) @@ -2251,8 +2263,15 @@ clutter_texture_set_area_from_rgb_data (ClutterTexture *texture, if ((flags & CLUTTER_TEXTURE_RGB_FLAG_PREMULT)) source_format |= COGL_PREMULT_BIT; - clutter_actor_realize (CLUTTER_ACTOR (texture)); + /* attempt to realize ... */ + if (!CLUTTER_ACTOR_IS_REALIZED (texture) && + clutter_actor_get_stage (CLUTTER_ACTOR (texture)) != NULL) + clutter_actor_realize (CLUTTER_ACTOR (texture)); + /* due to the fudging of clutter_texture_set_cogl_texture() + * which allows setting a texture pre-realize, we may end + * up having a texture even if we couldn't realize yet. + */ cogl_texture = clutter_texture_get_cogl_texture (texture); if (cogl_texture == COGL_INVALID_HANDLE) { @@ -2329,8 +2348,6 @@ on_fbo_source_size_change (GObject *object, if (priv->fbo_handle == COGL_INVALID_HANDLE) { g_warning ("%s: Offscreen texture creation failed", G_STRLOC); - CLUTTER_ACTOR_UNSET_FLAGS (CLUTTER_ACTOR (texture), - CLUTTER_ACTOR_REALIZED); return; } diff --git a/clutter/eglnative/clutter-stage-egl.c b/clutter/eglnative/clutter-stage-egl.c index 1d7f59603..7e766ed71 100644 --- a/clutter/eglnative/clutter-stage-egl.c +++ b/clutter/eglnative/clutter-stage-egl.c @@ -49,7 +49,8 @@ clutter_stage_egl_unrealize (ClutterActor *actor) CLUTTER_MARK(); - CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize (actor); + if (CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize != NULL) + CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize (actor); if (stage_egl->egl_surface) { @@ -253,8 +254,6 @@ clutter_stage_egl_dispose (GObject *gobject) { ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (gobject); - clutter_actor_unrealize (CLUTTER_ACTOR (stage_egl)); - G_OBJECT_CLASS (clutter_stage_egl_parent_class)->dispose (gobject); } diff --git a/clutter/eglx/clutter-stage-egl.c b/clutter/eglx/clutter-stage-egl.c index 5add2a862..97a8631ca 100644 --- a/clutter/eglx/clutter-stage-egl.c +++ b/clutter/eglx/clutter-stage-egl.c @@ -38,7 +38,8 @@ clutter_stage_egl_unrealize (ClutterActor *actor) g_object_get (stage_x11->wrapper, "offscreen", &was_offscreen, NULL); - CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize (actor); + if (CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize != NULL) + CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize (actor); clutter_x11_trap_x_errors (); @@ -275,9 +276,6 @@ clutter_stage_egl_dispose (GObject *gobject) ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (gobject); ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (gobject); - if (stage_x11->xwin) - clutter_actor_unrealize (CLUTTER_ACTOR (stage_egl)); - G_OBJECT_CLASS (clutter_stage_egl_parent_class)->dispose (gobject); } diff --git a/clutter/fruity/clutter-fruity.c b/clutter/fruity/clutter-fruity.c index 1198c5903..75cbcf1a3 100644 --- a/clutter/fruity/clutter-fruity.c +++ b/clutter/fruity/clutter-fruity.c @@ -393,6 +393,10 @@ typedef struct { stage_fruity = CLUTTER_STAGE_EGL(backend_fruity->stage); alive = FALSE; + /* FIXME why is this unrealize here? is the intent to destroy the stage? + * or hide it? Trying to clean up all manual unrealization so + * clutter_actor_unrealize() can be made private to clutter-actor.c + */ clutter_actor_unrealize (CLUTTER_ACTOR (stage_fruity)); clutter_main_quit (); } diff --git a/clutter/fruity/clutter-stage-fruity.c b/clutter/fruity/clutter-stage-fruity.c index 1f6b192fb..ea1603a58 100644 --- a/clutter/fruity/clutter-stage-fruity.c +++ b/clutter/fruity/clutter-stage-fruity.c @@ -45,7 +45,8 @@ clutter_stage_egl_unrealize (ClutterActor *actor) CLUTTER_MARK(); - CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize (actor); + if (CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize != NULL) + CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize (actor); if (stage_egl->egl_surface) { @@ -222,8 +223,6 @@ clutter_stage_egl_dispose (GObject *gobject) { ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (gobject); - clutter_actor_unrealize (CLUTTER_ACTOR (stage_egl)); - G_OBJECT_CLASS (clutter_stage_egl_parent_class)->dispose (gobject); } diff --git a/clutter/glx/clutter-stage-glx.c b/clutter/glx/clutter-stage-glx.c index 12f54cb38..650cf9445 100644 --- a/clutter/glx/clutter-stage-glx.c +++ b/clutter/glx/clutter-stage-glx.c @@ -67,10 +67,8 @@ clutter_stage_glx_unrealize (ClutterActor *actor) g_object_get (stage_x11->wrapper, "offscreen", &was_offscreen, NULL); - /* Chain up so all children get unrealized, needed to move texture data - * across contexts - */ - CLUTTER_ACTOR_CLASS (clutter_stage_glx_parent_class)->unrealize (actor); + if (CLUTTER_ACTOR_CLASS (clutter_stage_glx_parent_class)->unrealize != NULL) + CLUTTER_ACTOR_CLASS (clutter_stage_glx_parent_class)->unrealize (actor); clutter_x11_trap_x_errors (); @@ -310,12 +308,6 @@ fail: static void clutter_stage_glx_dispose (GObject *gobject) { - ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (gobject); - ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (gobject); - - if (stage_x11->xwin) - clutter_actor_unrealize (CLUTTER_ACTOR (stage_glx)); - G_OBJECT_CLASS (clutter_stage_glx_parent_class)->dispose (gobject); } diff --git a/clutter/sdl/clutter-stage-sdl.c b/clutter/sdl/clutter-stage-sdl.c index 17a8bd0eb..215c28307 100644 --- a/clutter/sdl/clutter-stage-sdl.c +++ b/clutter/sdl/clutter-stage-sdl.c @@ -199,8 +199,6 @@ clutter_stage_sdl_dispose (GObject *gobject) { ClutterStageSDL *stage_sdl = CLUTTER_STAGE_SDL (gobject); - clutter_actor_unrealize (CLUTTER_ACTOR (stage_sdl)); - G_OBJECT_CLASS (clutter_stage_sdl_parent_class)->dispose (gobject); } diff --git a/clutter/win32/clutter-stage-win32.c b/clutter/win32/clutter-stage-win32.c index 5ce8a16c6..7d233c6c2 100644 --- a/clutter/win32/clutter-stage-win32.c +++ b/clutter/win32/clutter-stage-win32.c @@ -574,10 +574,8 @@ clutter_stage_win32_unrealize (ClutterActor *actor) CLUTTER_NOTE (BACKEND, "Unrealizing stage"); - /* Chain up so all children get unrealized, needed to move texture - * data across contexts - */ - CLUTTER_ACTOR_CLASS (clutter_stage_win32_parent_class)->unrealize (actor); + if (CLUTTER_ACTOR_CLASS (clutter_stage_win32_parent_class)->unrealize != NULL) + CLUTTER_ACTOR_CLASS (clutter_stage_win32_parent_class)->unrealize (actor); if (stage_win32->client_dc) { @@ -602,9 +600,6 @@ clutter_stage_win32_dispose (GObject *gobject) { ClutterStageWin32 *stage_win32 = CLUTTER_STAGE_WIN32 (gobject); - if (stage_win32->hwnd) - clutter_actor_unrealize (CLUTTER_ACTOR (gobject)); - G_OBJECT_CLASS (clutter_stage_win32_parent_class)->dispose (gobject); } @@ -749,6 +744,12 @@ clutter_win32_set_stage_foreign (ClutterStage *stage, impl = _clutter_stage_get_window (stage); stage_win32 = CLUTTER_STAGE_WIN32 (impl); + /* FIXME this needs updating to use _clutter_actor_rerealize(), + * see the analogous code in x11 backend. Probably best if + * win32 maintainer does it so they can be sure it compiles + * and works. + */ + clutter_actor_unrealize (actor); if (!GetClientRect (hwnd, &client_rect)) diff --git a/clutter/x11/clutter-stage-x11.c b/clutter/x11/clutter-stage-x11.c index 2c4592ace..56a33d9cf 100644 --- a/clutter/x11/clutter-stage-x11.c +++ b/clutter/x11/clutter-stage-x11.c @@ -297,8 +297,7 @@ clutter_stage_x11_allocate (ClutterActor *self, if (stage_x11->xpixmap != None) { /* Need to recreate to resize */ - clutter_actor_unrealize (self); - clutter_actor_realize (self); + _clutter_actor_rerealize (self, NULL, NULL); } } @@ -529,11 +528,6 @@ clutter_stage_x11_finalize (GObject *gobject) static void clutter_stage_x11_dispose (GObject *gobject) { - ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (gobject); - - if (stage_x11->xwin) - clutter_actor_unrealize (CLUTTER_ACTOR (stage_x11)); - G_OBJECT_CLASS (clutter_stage_x11_parent_class)->dispose (gobject); } @@ -671,6 +665,29 @@ clutter_x11_get_stage_visual (ClutterStage *stage) return CLUTTER_STAGE_X11 (impl)->xvisinfo; } +typedef struct { + ClutterStageX11 *stage_x11; + ClutterGeometry geom; + Window xwindow; +} ForeignWindowData; + +static void +set_foreign_window_callback (ClutterActor *actor, + void *data) +{ + ForeignWindowData *fwd = data; + + CLUTTER_NOTE (BACKEND, "Setting foreign window (0x%x)", (int) fwd->xwindow); + + fwd->stage_x11->xwin = fwd->xwindow; + fwd->stage_x11->is_foreign_xwin = TRUE; + + fwd->stage_x11->xwin_width = fwd->geom.width; + fwd->stage_x11->xwin_height = fwd->geom.height; + + clutter_actor_set_geometry (actor, &fwd->geom); +} + /** * clutter_x11_set_stage_foreign: * @stage: a #ClutterStage @@ -693,7 +710,7 @@ clutter_x11_set_stage_foreign (ClutterStage *stage, guint width, height, border, depth; Window root_return; Status status; - ClutterGeometry geom; + ForeignWindowData fwd; g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE); g_return_val_if_fail (xwindow != None, FALSE); @@ -721,20 +738,16 @@ clutter_x11_set_stage_foreign (ClutterStage *stage, return FALSE; } - clutter_actor_unrealize (actor); + fwd.stage_x11 = stage_x11; + fwd.xwindow = xwindow; + fwd.geom.x = x; + fwd.geom.y = y; + fwd.geom.width = width; + fwd.geom.height = height; - CLUTTER_NOTE (BACKEND, "Setting foreign window (0x%x)", (int) xwindow); - - stage_x11->xwin = xwindow; - stage_x11->is_foreign_xwin = TRUE; - - geom.x = x; - geom.y = y; - geom.width = stage_x11->xwin_width = width; - geom.height = stage_x11->xwin_height = height; - - clutter_actor_set_geometry (actor, &geom); - clutter_actor_realize (actor); + _clutter_actor_rerealize (actor, + set_foreign_window_callback, + &fwd); CLUTTER_SET_PRIVATE_FLAGS (actor, CLUTTER_ACTOR_SYNC_MATRICES); @@ -744,11 +757,11 @@ clutter_x11_set_stage_foreign (ClutterStage *stage, void clutter_stage_x11_map (ClutterStageX11 *stage_x11) { - /* set the mapped flag on the implementation */ - CLUTTER_ACTOR_SET_FLAGS (stage_x11, CLUTTER_ACTOR_MAPPED); + /* set the mapped state on the wrapper */ + clutter_actor_map (CLUTTER_ACTOR (stage_x11->wrapper)); - /* and on the wrapper itself */ - CLUTTER_ACTOR_SET_FLAGS (stage_x11->wrapper, CLUTTER_ACTOR_MAPPED); + /* and on the implementation second */ + clutter_actor_map (CLUTTER_ACTOR (stage_x11)); if (stage_x11->fullscreen_on_map) clutter_stage_fullscreen (CLUTTER_STAGE (stage_x11->wrapper)); @@ -762,8 +775,8 @@ void clutter_stage_x11_unmap (ClutterStageX11 *stage_x11) { /* like above, unset the MAPPED stage on both the implementation and - * the wrapper + * the wrapper, but unmap in reverse order from map */ - CLUTTER_ACTOR_UNSET_FLAGS (stage_x11, CLUTTER_ACTOR_MAPPED); - CLUTTER_ACTOR_UNSET_FLAGS (stage_x11->wrapper, CLUTTER_ACTOR_MAPPED); + clutter_actor_unmap (CLUTTER_ACTOR (stage_x11)); + clutter_actor_unmap (CLUTTER_ACTOR (stage_x11->wrapper)); } diff --git a/doc/clutter-actor-invariants.txt b/doc/clutter-actor-invariants.txt index c64f84cc6..b7c74659f 100644 --- a/doc/clutter-actor-invariants.txt +++ b/doc/clutter-actor-invariants.txt @@ -28,20 +28,56 @@ ClutterActor. a. Public ClutterActor Flags CLUTTER_ACTOR_REALIZED - Set by clutter_actor_realize(), unset by clutter_actor_unrealize(). - Means: the actor has GPU resources associated to its paint cycle. - Once realized an actor needs to be explicitly unrealized unless - being destroyed. Hide, reparent etc will not unrealize. + Means: the actor has GPU resources associated to its paint + cycle. + + Set by clutter_actor_realize(), unset by + clutter_actor_unrealize(). Generally set implicitly when the + actor becomes MAPPED (see below). + + May only be set if one of the actor's ancestors is a toplevel. + May only be set if all of the actor's ancestors are realized. + + Once realized an actor remains realized until removed from the + toplevel. Hide, reparent will not unrealize; but unparent or + destroy will unrealize since they remove the actor from the + toplevel. CLUTTER_ACTOR_MAPPED - Set by clutter_actor_show(), unset by clutter_actor_hide() - May only be set if CLUTTER_ACTOR_IS_REALIZED (actor). - Means: the actor has been set as visible + Means: the actor will be painted if the stage is mapped. + + On non-toplevels, will be set if all of the following are + true, and unset otherwise: + * the actor's VISIBLE flag is set + * all of the actor's non-toplevel ancestors have the MAPPED + flag set + * the actor has a toplevel ancestor + * the toplevel ancestor's VISIBLE flag is set + * the toplevel ancestor's REALIZED flag is set + + On toplevels, MAPPED is set asynchronously when the window + system notifies Clutter that the toplevel has been made + visible on the screen. + + The MAPPED flag requires that an actor is REALIZED. When + Clutter sets the MAPPED flag, it forces realization; this is + the "normal" way for realization to occur, though explicit + realization with clutter_actor_realize() is permitted. CLUTTER_ACTOR_VISIBLE - Implies: CLUTTER_ACTOR_IS_REALIZED (actor) - && - CLUTTER_ACTOR_IS_MAPPED (actor) + Means: the actor's "visible" property was set to true by + the application programmer. + + Set by clutter_actor_show(), unset by clutter_actor_hide(). + + This is an application-controlled property, while MAPPED and + REALIZED are usually managed by Clutter (with the exception + that applications can "realize early" with + clutter_actor_realize()). + + If VISIBLE is unset, the actor (and any children) must + be immediately unmapped, to maintain the invariants for + the MAPPED flag. CLUTTER_ACTOR_REACTIVE Set and unset by clutter_actor_set_reactive() @@ -51,7 +87,7 @@ CLUTTER_ACTOR_REACTIVE parent implementation. In the case of ClutterGroup it being marked unreactive does not mark all children unreactive. * Clutter stage is always reactive. - + b. Private ClutterActor flags @@ -63,7 +99,18 @@ CLUTTER_ACTOR_IS_TOPLEVEL Set internally by the initialization of ClutterStage CLUTTER_ACTOR_IN_REPARENT - Set internally by clutter_actor_reparent() + Set internally by clutter_actor_reparent(). This flag + optimizes the reparent process by avoiding the need + to pass through an unrealized state when the actor is + removed from the old parent. + +CLUTTER_ACTOR_ABOUT_TO_UNPARENT + Set internally during part of clutter_actor_unparent(). + Causes the actor to pretend it has no parent, then + update invariants; which effectively forces the actor + to unrealize. The purpose of this is to unrealize _before_ the + actor is removed from the stage, so unrealize implementations + can use clutter_actor_get_stage(). CLUTTER_ACTOR_SYNC_MATRICES Set internally by ClutterStage implementations @@ -101,68 +148,118 @@ In the following 1) CLUTTER_ACTOR_IN_DESTRUCTION => !CLUTTER_ACTOR_IS_MAPPED (actor) && !CLUTTER_ACTOR_IS_REALIZED (actor) - clutter_actor_destroy() will cause an actor to be hidden - and unrealized. + clutter_actor_destroy() will cause an actor to be unparented, + which means the actor must be unmapped and unrealized as + well. 2) CLUTTER_ACTOR_IS_MAPPED (actor) => CLUTTER_ACTOR_IS_REALIZED (actor) - calling clutter_actor_show() on an unrealized actor will cause - a realization to happen. + when an actor is mapped, it must first be realized. + + This is the most common way an actor becomes realized. 3) if clutter_actor_set_parent (actor, parent): - CLUTTER_ACTOR_IS_REALIZED (parent) => CLUTTER_ACTOR_IS_REALIZED (actor) + ((parent_is_not_toplevel && CLUTTER_ACTOR_IS_MAPPED (parent)) || + (parent_is_toplevel && CLUTTER_ACTOR_IS_VISIBLE(parent))) && + CLUTTER_ACTOR_IS_VISIBLE (actor) + => CLUTTER_ACTOR_IS_MAPPED (actor) - calling clutter_actor_set_parent() on an actor and a realized - parent will cause a realization on the actor. + calling clutter_actor_set_parent() on an actor and a mapped + parent will map the actor if it has been shown. 4) if clutter_actor_unparent (actor): CLUTTER_ACTOR_IS_MAPPED (actor) <=> CLUTTER_ACTOR_IN_REPARENT - calling clutter_actor_unparent() on an actor will hide the actor; - calling clutter_actor_reparent() on an actor will leave the actor - in the same state. + calling clutter_actor_unparent() on an actor will unmap and + unrealize the actor since it no longer has a toplevel. - Neither will unrealize. + calling clutter_actor_reparent() on an actor will leave the + actor mapped and realized (if it was before) until it has a + new parent, at which point the invariants implied by the new + parent's state are applied. + +5) CLUTTER_ACTOR_IS_REALIZED(actor) => CLUTTER_ACTOR_IS_REALIZED(parent) + + Actors may only be realized if their parent is realized. + However, they may be unrealized even though their parent + is realized. + + This implies that an actor may not be realized unless + it has a parent, or is a toplevel. + + Since only toplevels can realize without a parent, no actor + can be realized unless it either is a toplevel or has a + toplevel ancestor. + + As long as they are unmapped, actors may be unrealized. This + will force all children of the actor to be unrealized, since + children may not be realized while parents are unrealized. + +6) CLUTTER_ACTOR_IS_MAPPED(actor) <=> + ( ( (CLUTTER_ACTOR_IS_VISIBLE(toplevel_parent) && + CLUTTER_ACTOR_IS_REALIZED(toplevel_parent)) || + CLUTTER_ACTOR_IS_MAPPED(non_toplevel_parent) ) ) && + CLUTTER_ACTOR_IS_VISIBLE(actor) + + Actors _must_ be mapped if and only if they are visible and + their parent is mapped, or they are visible and their + parent is a toplevel that's realized and visible. + + This invariant enables us to track whether an actor will + be painted (whether it's MAPPED) without ever traversing + the actor graph. iii. State changes ------------------------------------------------------------------------------- clutter_actor_show: - 1. if !CLUTTER_ACTOR_REALIZED calls clutter_actor_realize - 2. sets CLUTTER_ACTOR_MAPPED + 1. sets VISIBLE + 2. sets MAPPED if invariants are met; mapping in turn sets + REALIZED clutter_actor_hide: - 1. sets !CLUTTER_ACTOR_MAPPED + 1. sets !VISIBLE + 2. unsets MAPPED if actor was mapped previously + 3. does not affect REALIZED clutter_actor_destroy: 1. sets CLUTTER_ACTOR_IN_DESTRUCTION + 2. unparents the actor, which in turn implies unmap and unrealize clutter_actor_realize: - sets CLUTTER_ACTOR_REALIZED + 1. attempts to set REALIZED on all parents, failing if + invariants are not met, e.g. not in a toplevel yet + 2. sets REALIZED on actor if parent was successfully realized -clutter_actor_unrealized: - 1. if CLUTTER_ACTOR_MAPPED calls clutter_actor_hide - 2. sets !CLUTTER_ACTOR_REALIZED +clutter_actor_unrealize: + 1. sets !VISIBLE which forces !MAPPED + 2. sets !REALIZED + 3. !MAPPED and !REALIZED forces unmap and unrealize of all + children clutter_actor_set_parent: 1. sets actor->parent - 2. if parent is CLUTTER_ACTOR_REALIZED calls clutter_actor_realize - 3. if actor->show_on_set_parent is TRUE calls clutter_actor_show + 2. if actor->show_on_set_parent is TRUE calls clutter_actor_show + 3. sets MAPPED if all prerequisites are now met for map 4. if !CLUTTER_ACTOR_IN_REPARENT emits ::parent-set with old_parent set to NULL -clutter_actor_unset_parent: +clutter_actor_unparent: 1. unsets actor->parent - 2. if !CLUTTER_ACTOR_IN_REPARENT calls clutter_actor_hide + 2. if !CLUTTER_ACTOR_IN_REPARENT, sets !MAPPED and !REALIZED + since the invariants for those flags are no longer met 3. if !CLUTTER_ACTOR_IN_REPARENT emits ::parent-set with old_parent set to the previous parent clutter_actor_reparent: 1. sets CLUTTER_ACTOR_IN_REPARENT 2. emits ::parent-set with old_parent set to the previous parent - equivalent to: + equivalent to: clutter_actor_unparent clutter_actor_set_parent + 3. updates state of the actor to match invariants + (may change MAPPED or REALIZED in either direction, + depending on state of the new parent) iv. Responsibilities of a ClutterActor @@ -173,24 +270,27 @@ clutter_actor_reparent: When adding an actor to a container, the container must: 1. call clutter_actor_set_parent (actor, container) - 2. call clutter_actor_queue_relayout (container) + 2. call clutter_actor_queue_relayout (container) if + adding the actor changes the container's preferred + size b. Removing from a container When removing an actor from a container, the container must: 1. call clutter_actor_unparent (actor) - 2. call clutter_actor_queue_relayout (container) + 2. call clutter_actor_queue_relayout (container) if removing + the actor changes the container's preferred size Notes: * here a container actor is any actor that contains children actors; it does not imply the implementation of the ClutterContainer interface. -* clutter_actor_unparent() will hide the actor except in the special case - when CLUTTER_ACTOR_IN_REPARENT is set. +* clutter_actor_unparent() will unmap and unrealize the actor except + in the special case when CLUTTER_ACTOR_IN_REPARENT is set. -* 'Composite' Clutter actors need to pass down any allocations to children. +* 'Composite' Clutter actors need to pass down any allocations to children. c. Initial state diff --git a/tests/conform/test-actor-invariants.c b/tests/conform/test-actor-invariants.c index 315abeed6..77eea9ad2 100644 --- a/tests/conform/test-actor-invariants.c +++ b/tests/conform/test-actor-invariants.c @@ -21,15 +21,38 @@ test_initial_state (TestConformSimpleFixture *fixture, } void -test_realized (TestConformSimpleFixture *fixture, - gconstpointer data) +test_shown_not_parented (TestConformSimpleFixture *fixture, + gconstpointer data) { ClutterActor *actor; actor = clutter_rectangle_new (); + clutter_actor_show (actor); + + g_assert (!CLUTTER_ACTOR_IS_REALIZED (actor)); + g_assert (!CLUTTER_ACTOR_IS_MAPPED (actor)); + g_assert (CLUTTER_ACTOR_IS_VISIBLE (actor)); + + clutter_actor_destroy (actor); +} + +void +test_realized (TestConformSimpleFixture *fixture, + gconstpointer data) +{ + ClutterActor *actor; + ClutterActor *stage; + + stage = clutter_stage_get_default (); + + actor = clutter_rectangle_new (); + g_assert (!(CLUTTER_ACTOR_IS_REALIZED (actor))); + clutter_actor_hide (actor); /* don't show, so won't map */ + clutter_container_add_actor (CLUTTER_CONTAINER (stage), + actor); clutter_actor_realize (actor); g_assert (CLUTTER_ACTOR_IS_REALIZED (actor)); @@ -45,33 +68,130 @@ test_mapped (TestConformSimpleFixture *fixture, gconstpointer data) { ClutterActor *actor; + ClutterActor *stage; + + stage = clutter_stage_get_default (); actor = clutter_rectangle_new (); g_assert (!(CLUTTER_ACTOR_IS_REALIZED (actor))); g_assert (!(CLUTTER_ACTOR_IS_MAPPED (actor))); - clutter_actor_show (actor); + clutter_container_add_actor (CLUTTER_CONTAINER (stage), + actor); g_assert (CLUTTER_ACTOR_IS_REALIZED (actor)); g_assert (CLUTTER_ACTOR_IS_MAPPED (actor)); - g_assert (CLUTTER_ACTOR_IS_VISIBLE (actor)); clutter_actor_destroy (actor); } +void +test_realize_not_recursive (TestConformSimpleFixture *fixture, + gconstpointer data) +{ + ClutterActor *actor, *group; + ClutterActor *stage; + + stage = clutter_stage_get_default (); + + group = clutter_group_new (); + + actor = clutter_rectangle_new (); + + clutter_actor_hide (group); /* don't show, so won't map */ + clutter_actor_hide (actor); /* don't show, so won't map */ + + g_assert (!(CLUTTER_ACTOR_IS_REALIZED (group))); + g_assert (!(CLUTTER_ACTOR_IS_REALIZED (actor))); + + clutter_container_add_actor (CLUTTER_CONTAINER (stage), + group); + clutter_container_add_actor (CLUTTER_CONTAINER (group), + actor); + + clutter_actor_realize (group); + + g_assert (CLUTTER_ACTOR_IS_REALIZED (group)); + + g_assert (!(CLUTTER_ACTOR_IS_MAPPED (group))); + g_assert (!(CLUTTER_ACTOR_IS_VISIBLE (group))); + + /* realizing group did not realize the child */ + g_assert (!CLUTTER_ACTOR_IS_REALIZED (actor)); + g_assert (!(CLUTTER_ACTOR_IS_MAPPED (actor))); + g_assert (!(CLUTTER_ACTOR_IS_VISIBLE (actor))); + + clutter_actor_destroy (group); +} + +void +test_map_recursive (TestConformSimpleFixture *fixture, + gconstpointer data) +{ + ClutterActor *actor, *group; + ClutterActor *stage; + + stage = clutter_stage_get_default (); + + group = clutter_group_new (); + + actor = clutter_rectangle_new (); + + clutter_actor_hide (group); /* hide at first */ + clutter_actor_show (actor); /* show at first */ + + g_assert (!(CLUTTER_ACTOR_IS_REALIZED (group))); + g_assert (!(CLUTTER_ACTOR_IS_REALIZED (actor))); + g_assert (!(CLUTTER_ACTOR_IS_MAPPED (group))); + g_assert (!(CLUTTER_ACTOR_IS_MAPPED (actor))); + g_assert (!(CLUTTER_ACTOR_IS_VISIBLE (group))); + g_assert ((CLUTTER_ACTOR_IS_VISIBLE (actor))); + + clutter_container_add_actor (CLUTTER_CONTAINER (stage), + group); + clutter_container_add_actor (CLUTTER_CONTAINER (group), + actor); + + g_assert (!(CLUTTER_ACTOR_IS_REALIZED (group))); + g_assert (!(CLUTTER_ACTOR_IS_REALIZED (actor))); + g_assert (!(CLUTTER_ACTOR_IS_MAPPED (group))); + g_assert (!(CLUTTER_ACTOR_IS_MAPPED (actor))); + g_assert (!(CLUTTER_ACTOR_IS_VISIBLE (group))); + g_assert ((CLUTTER_ACTOR_IS_VISIBLE (actor))); + + /* show group, which should map and realize both + * group and child. + */ + clutter_actor_show (group); + g_assert (CLUTTER_ACTOR_IS_REALIZED (group)); + g_assert (CLUTTER_ACTOR_IS_REALIZED (actor)); + g_assert (CLUTTER_ACTOR_IS_MAPPED (group)); + g_assert (CLUTTER_ACTOR_IS_MAPPED (actor)); + g_assert (CLUTTER_ACTOR_IS_VISIBLE (group)); + g_assert (CLUTTER_ACTOR_IS_VISIBLE (actor)); + + clutter_actor_destroy (group); +} + void test_show_on_set_parent (TestConformSimpleFixture *fixture, gconstpointer data) { ClutterActor *actor, *group; gboolean show_on_set_parent; + ClutterActor *stage; + + stage = clutter_stage_get_default (); group = clutter_group_new (); g_assert (!(CLUTTER_ACTOR_IS_VISIBLE (group))); + clutter_container_add_actor (CLUTTER_CONTAINER (stage), + group); + actor = clutter_rectangle_new (); g_object_get (G_OBJECT (actor), "show-on-set-parent", &show_on_set_parent, @@ -94,8 +214,9 @@ test_show_on_set_parent (TestConformSimpleFixture *fixture, "show-on-set-parent", &show_on_set_parent, NULL); - g_assert (CLUTTER_ACTOR_IS_REALIZED (actor)); - g_assert (!(CLUTTER_ACTOR_IS_VISIBLE (actor))); + g_assert (!CLUTTER_ACTOR_IS_REALIZED (actor)); + g_assert (!CLUTTER_ACTOR_IS_MAPPED (actor)); + g_assert (CLUTTER_ACTOR_IS_VISIBLE (actor)); g_assert (show_on_set_parent == TRUE); clutter_actor_destroy (actor); diff --git a/tests/conform/test-conform-main.c b/tests/conform/test-conform-main.c index d4ea35dfc..8a75b8868 100644 --- a/tests/conform/test-conform-main.c +++ b/tests/conform/test-conform-main.c @@ -116,7 +116,10 @@ main (int argc, char **argv) TEST_CONFORM_SIMPLE ("/fixed", test_fixed_constants); TEST_CONFORM_SIMPLE ("/invariants", test_initial_state); + TEST_CONFORM_SIMPLE ("/invariants", test_shown_not_parented); TEST_CONFORM_SIMPLE ("/invariants", test_realized); + TEST_CONFORM_SIMPLE ("/invariants", test_realize_not_recursive); + TEST_CONFORM_SIMPLE ("/invariants", test_map_recursive); TEST_CONFORM_SIMPLE ("/invariants", test_mapped); TEST_CONFORM_SIMPLE ("/invariants", test_show_on_set_parent);