diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c index 0305b748e..fc2995b98 100644 --- a/clutter/clutter-actor.c +++ b/clutter/clutter-actor.c @@ -798,6 +798,11 @@ struct _ClutterActorPrivate */ gulong in_cloned_branch; + GListModel *child_model; + ClutterActorCreateChildFunc create_child_func; + gpointer create_child_data; + GDestroyNotify create_child_notify; + /* bitfields: KEEP AT THE END */ /* fixed position and sizes */ @@ -5928,6 +5933,18 @@ clutter_actor_dispose (GObject *object) g_clear_object (&priv->effects); g_clear_object (&priv->flatten_effect); + if (priv->child_model != NULL) + { + if (priv->create_child_notify != NULL) + priv->create_child_notify (priv->create_child_data); + + priv->create_child_func = NULL; + priv->create_child_data = NULL; + priv->create_child_notify = NULL; + + g_clear_object (&priv->child_model); + } + if (priv->layout_manager != NULL) { clutter_layout_manager_set_container (priv->layout_manager, NULL); @@ -20776,3 +20793,113 @@ _clutter_actor_get_active_framebuffer (ClutterActor *self) return _clutter_stage_get_active_framebuffer (stage); } + +static void +clutter_actor_bound_model__changed (GListModel *model, + guint position, + guint removed, + guint added, + gpointer user_data) +{ + ClutterActor *parent = user_data; + ClutterActorPrivate *priv = parent->priv; + guint i; + + while (removed--) + { + ClutterActor *child = clutter_actor_get_child_at_index (parent, position); + clutter_actor_destroy (child); + } + + for (i = 0; i < added; i++) + { + GObject *item = g_list_model_get_item (model, position + i); + ClutterActor *child = priv->create_child_func (item, priv->create_child_data); + + /* The actor returned by the function can have a floating reference, + * if the implementation is in pure C, or have a full reference, usually + * the case for language bindings. To avoid leaking references, we + * try to assume ownership of the instance, and release the reference + * at the end unconditionally, leaving the only reference to the actor + * itself. + */ + if (g_object_is_floating (child)) + g_object_ref_sink (child); + + clutter_actor_insert_child_at_index (parent, child, position + i); + + g_object_unref (child); + g_object_unref (item); + } +} + +/** + * clutter_actor_bind_model: + * @self: a #ClutterActor + * @model: (optional): a #GListModel + * @create_child_func: a function that creates #ClutterActor instances + * from the contents of the @model + * @user_data: user data passed to @create_child_func + * @notify: function called when unsetting the @model + * + * Binds a #GListModel to a #ClutterActor. + * + * If the #ClutterActor was already bound to a #GListModel, the previous + * binding is destroyed. + * + * The existing children of #ClutterActor are destroyed when setting a + * model, and new children are created and added, representing the contents + * of the @model. The #ClutterActor is updated whenever the @model changes. + * If @model is %NULL, the #ClutterActor is left empty. + * + * When a #ClutterActor is bound to a model, adding and removing children + * directly is undefined behaviour. + * + * Since: 1.24 + */ +void +clutter_actor_bind_model (ClutterActor *self, + GListModel *model, + ClutterActorCreateChildFunc create_child_func, + gpointer user_data, + GDestroyNotify notify) +{ + ClutterActorPrivate *priv = clutter_actor_get_instance_private (self); + + g_return_if_fail (CLUTTER_IS_ACTOR (self)); + g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); + g_return_if_fail (model == NULL || create_child_func != NULL); + + if (priv->child_model != NULL) + { + if (priv->create_child_notify != NULL) + priv->create_child_notify (priv->create_child_data); + + g_signal_handlers_disconnect_by_func (priv->child_model, + clutter_actor_bound_model__changed, + self); + g_clear_object (&priv->child_model); + priv->create_child_func = NULL; + priv->create_child_data = NULL; + priv->create_child_notify = NULL; + } + + clutter_actor_destroy_all_children (self); + + if (model == NULL) + return; + + priv->child_model = g_object_ref (model); + priv->create_child_func = create_child_func; + priv->create_child_data = user_data; + priv->create_child_notify = notify; + + g_signal_connect (priv->child_model, "items-changed", + G_CALLBACK (clutter_actor_bound_model__changed), + self); + + clutter_actor_bound_model__changed (priv->child_model, + 0, + 0, g_list_model_get_n_items (priv->child_model), + self); +} diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h index a71ca270d..cd5e10d5d 100644 --- a/clutter/clutter-actor.h +++ b/clutter/clutter-actor.h @@ -31,6 +31,7 @@ /* clutter-actor.h */ +#include #include #include @@ -851,6 +852,32 @@ CLUTTER_AVAILABLE_IN_1_22 gint clutter_actor_get_opacity_override (ClutterActor *self); #endif +/** + * ClutterActorCreateChildFunc: + * @item: (type GObject): the item in the model + * @user_data: Data passed to clutter_actor_bind_model() + * + * Creates a #ClutterActor using the @item in the model. + * + * The usual way to implement this function is to create a #ClutterActor + * instance and then bind the #GObject properties to the actor properties + * of interest, using g_object_bind_property(). This way, when the @item + * in the #GListModel changes, the #ClutterActor changes as well. + * + * Returns: (transfer full): The newly created child #ClutterActor + * + * Since: 1.24 + */ +typedef ClutterActor * (* ClutterActorCreateChildFunc) (gpointer item, + gpointer user_data); + +CLUTTER_AVAILABLE_IN_1_24 +void clutter_actor_bind_model (ClutterActor *self, + GListModel *model, + ClutterActorCreateChildFunc create_child_func, + gpointer user_data, + GDestroyNotify notify); + G_END_DECLS #endif /* __CLUTTER_ACTOR_H__ */ diff --git a/doc/reference/clutter-sections.txt b/doc/reference/clutter-sections.txt index 0f807df55..0e727fba1 100644 --- a/doc/reference/clutter-sections.txt +++ b/doc/reference/clutter-sections.txt @@ -465,6 +465,8 @@ clutter_actor_iter_next clutter_actor_iter_prev clutter_actor_iter_remove clutter_actor_iter_destroy +ClutterActorCreateChildFunc +clutter_actor_bind_model clutter_actor_save_easing_state