diff --git a/examples/Makefile.am b/examples/Makefile.am index 80602a2f4..b83468b4b 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -1,6 +1,7 @@ include $(top_srcdir)/build/autotools/Makefile.am.silent all_examples = \ + actor-model \ basic-actor \ box-layout \ canvas \ diff --git a/examples/actor-model.c b/examples/actor-model.c new file mode 100644 index 000000000..f87fc64d9 --- /dev/null +++ b/examples/actor-model.c @@ -0,0 +1,442 @@ +#include +#include +#include + +/* {{{ MenuItem */ +#define EXAMPLE_TYPE_MENU_ITEM (example_menu_item_get_type ()) + +G_DECLARE_FINAL_TYPE (ExampleMenuItem, example_menu_item, EXAMPLE, MENU_ITEM, ClutterText) + +struct _ExampleMenuItem +{ + ClutterText parent_instance; + + gboolean is_selected; +}; + +struct _ExampleMenuItemClass +{ + ClutterTextClass parent_class; +}; + +G_DEFINE_TYPE (ExampleMenuItem, example_menu_item, CLUTTER_TYPE_TEXT) + +enum { + MENU_ITEM_PROP_SELECTED = 1, + MENU_ITEM_N_PROPS +}; + +static GParamSpec *menu_item_props[MENU_ITEM_N_PROPS] = { NULL, }; + +static void +example_menu_item_set_selected (ExampleMenuItem *self, + gboolean selected) +{ + selected = !!selected; + if (self->is_selected == selected) + return; + + self->is_selected = selected; + + if (self->is_selected) + clutter_text_set_color (CLUTTER_TEXT (self), CLUTTER_COLOR_LightSkyBlue); + else + clutter_text_set_color (CLUTTER_TEXT (self), CLUTTER_COLOR_White); + + g_object_notify_by_pspec (G_OBJECT (self), menu_item_props[MENU_ITEM_PROP_SELECTED]); +} + +static void +example_menu_item_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + case MENU_ITEM_PROP_SELECTED: + example_menu_item_set_selected (EXAMPLE_MENU_ITEM (gobject), + g_value_get_boolean (value)); + break; + } +} + +static void +example_menu_item_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + case MENU_ITEM_PROP_SELECTED: + g_value_set_boolean (value, EXAMPLE_MENU_ITEM (gobject)->is_selected); + break; + } +} + +static void +example_menu_item_class_init (ExampleMenuItemClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = example_menu_item_set_property; + gobject_class->get_property = example_menu_item_get_property; + + menu_item_props[MENU_ITEM_PROP_SELECTED] = + g_param_spec_boolean ("selected", NULL, NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, MENU_ITEM_N_PROPS, menu_item_props); +} + +static void +example_menu_item_init (ExampleMenuItem *self) +{ + ClutterText *text = CLUTTER_TEXT (self); + ClutterActor *actor = CLUTTER_ACTOR (self); + + clutter_text_set_font_name (text, "Sans Bold 24px"); + clutter_text_set_color (text, CLUTTER_COLOR_White); + + clutter_actor_set_margin_left (actor, 12); + clutter_actor_set_margin_right (actor, 12); +} + +/* }}} */ + +/* {{{ Menu */ +#define EXAMPLE_TYPE_MENU (example_menu_get_type ()) + +G_DECLARE_FINAL_TYPE (ExampleMenu, example_menu, EXAMPLE, MENU, ClutterActor) + +struct _ExampleMenu +{ + ClutterActor parent_instance; + + int current_idx; +}; + +struct _ExampleMenuClass +{ + ClutterActorClass parent_class; +}; + +G_DEFINE_TYPE (ExampleMenu, example_menu, CLUTTER_TYPE_ACTOR) + +static void +example_menu_class_init (ExampleMenuClass *klass) +{ +} + +static void +example_menu_init (ExampleMenu *self) +{ + ClutterActor *actor = CLUTTER_ACTOR (self); + ClutterLayoutManager *layout; + + layout = clutter_box_layout_new (); + clutter_box_layout_set_orientation (CLUTTER_BOX_LAYOUT (layout), CLUTTER_ORIENTATION_VERTICAL); + clutter_box_layout_set_spacing (CLUTTER_BOX_LAYOUT (layout), 12); + + clutter_actor_set_layout_manager (actor, layout); + clutter_actor_set_background_color (actor, CLUTTER_COLOR_Black); + + self->current_idx = -1; +} + +static ClutterActor * +example_menu_select_item (ExampleMenu *self, + int idx) +{ + ClutterActor *item; + + if (idx == self->current_idx) + return clutter_actor_get_child_at_index (CLUTTER_ACTOR (self), self->current_idx); + + item = clutter_actor_get_child_at_index (CLUTTER_ACTOR (self), self->current_idx); + if (item != NULL) + example_menu_item_set_selected ((ExampleMenuItem *) item, FALSE); + + if (idx < 0) + idx = clutter_actor_get_n_children (CLUTTER_ACTOR (self)) - 1; + else if (idx >= clutter_actor_get_n_children (CLUTTER_ACTOR (self))) + idx = 0; + + self->current_idx = idx; + + item = clutter_actor_get_child_at_index (CLUTTER_ACTOR (self), self->current_idx); + if (item != NULL) + example_menu_item_set_selected ((ExampleMenuItem *) item, TRUE); + + return item; +} + +static ClutterActor * +example_menu_select_next (ExampleMenu *self) +{ + return example_menu_select_item (self, self->current_idx + 1); +} + +static ClutterActor * +example_menu_select_prev (ExampleMenu *self) +{ + return example_menu_select_item (self, self->current_idx - 1); +} + +/* }}} */ + +/* {{{ MenuItemModel */ +#define EXAMPLE_TYPE_MENU_ITEM_MODEL (example_menu_item_model_get_type ()) + +G_DECLARE_FINAL_TYPE (ExampleMenuItemModel, example_menu_item_model, EXAMPLE, MENU_ITEM_MODEL, GObject) + +struct _ExampleMenuItemModel +{ + GObject parent_instance; + + char *label; + + gboolean selected; +}; + +struct _ExampleMenuItemModelClass +{ + GObjectClass parent_class; +}; + +enum { + MENU_ITEM_MODEL_PROP_LABEL = 1, + MENU_ITEM_MODEL_PROP_SELECTED, + MENU_ITEM_MODEL_N_PROPS +}; + +static GParamSpec *menu_item_model_props[MENU_ITEM_MODEL_N_PROPS] = { NULL, }; + +G_DEFINE_TYPE (ExampleMenuItemModel, example_menu_item_model, G_TYPE_OBJECT) + +static void +example_menu_item_model_finalize (GObject *gobject) +{ + ExampleMenuItemModel *self = (ExampleMenuItemModel *) gobject; + + g_free (self->label); + + G_OBJECT_CLASS (example_menu_item_model_parent_class)->finalize (gobject); +} + +static void +example_menu_item_model_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ExampleMenuItemModel *self = (ExampleMenuItemModel *) gobject; + + switch (prop_id) + { + case MENU_ITEM_MODEL_PROP_LABEL: + g_free (self->label); + self->label = g_value_dup_string (value); + break; + + case MENU_ITEM_MODEL_PROP_SELECTED: + self->selected = g_value_get_boolean (value); + break; + } +} + +static void +example_menu_item_model_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ExampleMenuItemModel *self = (ExampleMenuItemModel *) gobject; + + switch (prop_id) + { + case MENU_ITEM_MODEL_PROP_LABEL: + g_value_set_string (value, self->label); + break; + + case MENU_ITEM_MODEL_PROP_SELECTED: + g_value_set_boolean (value, self->selected); + break; + } +} + +static void +example_menu_item_model_class_init (ExampleMenuItemModelClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = example_menu_item_model_set_property; + gobject_class->get_property = example_menu_item_model_get_property; + gobject_class->finalize = example_menu_item_model_finalize; + + menu_item_model_props[MENU_ITEM_MODEL_PROP_LABEL] = + g_param_spec_string ("label", NULL, NULL, + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + menu_item_model_props[MENU_ITEM_MODEL_PROP_SELECTED] = + g_param_spec_boolean ("selected", NULL, NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, MENU_ITEM_MODEL_N_PROPS, menu_item_model_props); +} + +static void +example_menu_item_model_init (ExampleMenuItemModel *self) +{ +} +/* }}} */ + +/* {{{ main */ +static gboolean +on_key_press (ClutterActor *stage, + ClutterEvent *event) +{ + ClutterActor *scroll = clutter_actor_get_first_child (stage); + ClutterActor *menu = clutter_actor_get_first_child (scroll); + ClutterActor *item = NULL; + guint key = clutter_event_get_key_symbol (event); + ClutterPoint p; + + switch (key) + { + case CLUTTER_KEY_q: + clutter_main_quit (); + break; + + case CLUTTER_KEY_Up: + item = example_menu_select_prev ((ExampleMenu *) menu); + clutter_actor_get_position (item, &p.x, &p.y); + break; + + case CLUTTER_KEY_Down: + item = example_menu_select_next ((ExampleMenu *) menu); + clutter_actor_get_position (item, &p.x, &p.y); + break; + } + + if (item != NULL) + clutter_scroll_actor_scroll_to_point (CLUTTER_SCROLL_ACTOR (scroll), &p); + + return CLUTTER_EVENT_PROPAGATE; +} + +static void +on_model_item_selection (GObject *model_item, + GParamSpec *pspec, + gpointer data) +{ + char *label = NULL; + gboolean is_selected = FALSE; + + g_object_get (model_item, "label", &label, "selected", &is_selected, NULL); + + if (is_selected) + g_print ("Item '%s' selected!\n", label); + + g_free (label); +} + +static ClutterActor * +create_menu_item (gpointer item, + gpointer data G_GNUC_UNUSED) +{ + ClutterActor *res = g_object_new (EXAMPLE_TYPE_MENU_ITEM, NULL); + + /* The label goes from the model to the view */ + g_object_bind_property (item, "label", + res, "text", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + + /* The selected state goes in either direction */ + g_object_bind_property (item, "selected", + res, "selected", + G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + + g_signal_connect (item, "notify::selected", G_CALLBACK (on_model_item_selection), NULL); + + return res; +} + +static ClutterActor * +create_menu_actor (void) +{ + GListStore *model = g_list_store_new (EXAMPLE_TYPE_MENU_ITEM_MODEL); + ClutterActor *menu = g_object_new (EXAMPLE_TYPE_MENU, NULL); + int i; + + /* Populate the model */ + for (i = 0; i < 12; i++) + { + char *label = g_strdup_printf ("Option %02d", i + 1); + + ExampleMenuItemModel *item = g_object_new (EXAMPLE_TYPE_MENU_ITEM_MODEL, + "label", label, + NULL); + + g_list_store_append (model, item); + + g_object_unref (item); + g_free (label); + } + + clutter_actor_bind_model (menu, G_LIST_MODEL (model), + create_menu_item, + NULL, NULL); + + /* The actor owns the model */ + g_object_unref (model); + + example_menu_select_item ((ExampleMenu *) menu, 0); + + return menu; +} + +static ClutterActor * +create_scroll_actor (void) +{ + ClutterActor *menu = clutter_scroll_actor_new (); + clutter_actor_set_name (menu, "scroll"); + clutter_scroll_actor_set_scroll_mode (CLUTTER_SCROLL_ACTOR (menu), + CLUTTER_SCROLL_VERTICALLY); + clutter_actor_set_easing_duration (menu, 250); + clutter_actor_add_child (menu, create_menu_actor ()); + + return menu; +} + +int +main (int argc, char *argv[]) +{ + ClutterActor *stage, *menu; + + if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS) + return 1; + + stage = clutter_stage_new (); + clutter_stage_set_title (CLUTTER_STAGE (stage), "Actor Model"); + clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE); + g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL); + g_signal_connect (stage, "key-press-event", G_CALLBACK (on_key_press), NULL); + clutter_actor_show (stage); + +#define PADDING 18.f + + menu = create_scroll_actor (); + clutter_actor_set_position (menu, 0, PADDING); + clutter_actor_add_constraint (menu, clutter_align_constraint_new (stage, CLUTTER_ALIGN_X_AXIS, 0.5)); + clutter_actor_add_constraint (menu, clutter_bind_constraint_new (stage, CLUTTER_BIND_HEIGHT, -PADDING * 2)); + clutter_actor_add_child (stage, menu); + + clutter_main (); + + return 0; +} +/* }}} */