#include #include #include typedef struct _MultiLayout MultiLayout; typedef struct _MultiLayoutClass MultiLayoutClass; typedef enum { MULTI_LAYOUT_GRID, MULTI_LAYOUT_CIRCLE } MultiLayoutState; struct _MultiLayout { ClutterLayoutManager parent_instance; /* the state of the layout */ MultiLayoutState state; /* spacing between children */ float spacing; /* cell size */ float cell_width; float cell_height; }; struct _MultiLayoutClass { ClutterLayoutManagerClass parent_class; }; GType multi_layout_get_type (void); ClutterLayoutManager * multi_layout_new (void); void multi_layout_set_state (MultiLayout *layout, MultiLayoutState state); void multi_layout_set_spacing (MultiLayout *layout, float spacing); G_DEFINE_TYPE (MultiLayout, multi_layout, CLUTTER_TYPE_LAYOUT_MANAGER) static void multi_layout_get_preferred_width (ClutterLayoutManager *manager, ClutterContainer *container, float for_height, float *min_width_p, float *nat_width_p) { MultiLayout *self = (MultiLayout *) manager; float minimum, natural; float max_natural_width; ClutterActorIter iter; ClutterActor *child; int n_children; minimum = natural = 0.f; max_natural_width = 0.f; n_children = 0; clutter_actor_iter_init (&iter, CLUTTER_ACTOR (container)); while (clutter_actor_iter_next (&iter, &child)) { float child_minimum, child_natural; if (!CLUTTER_ACTOR_IS_VISIBLE (child)) continue; clutter_actor_get_preferred_width (child, -1.f, &child_minimum, &child_natural); max_natural_width = MAX (max_natural_width, child_natural); if (self->state == MULTI_LAYOUT_GRID) { minimum += child_minimum; natural += child_natural; } else if (self->state == MULTI_LAYOUT_CIRCLE) { minimum = MAX (minimum, child_minimum); natural = MAX (natural, child_natural); } n_children += 1; } self->cell_width = max_natural_width; minimum += (self->spacing * (n_children - 1)); natural += (self->spacing * (n_children - 1)); if (min_width_p != NULL) *min_width_p = minimum; if (nat_width_p != NULL) *nat_width_p = natural; } static void multi_layout_get_preferred_height (ClutterLayoutManager *manager, ClutterContainer *container, float for_width, float *min_height_p, float *nat_height_p) { MultiLayout *self = (MultiLayout *) manager; float minimum, natural; ClutterActorIter iter; ClutterActor *child; int n_children; minimum = natural = self->spacing * 2.f; n_children = 0; clutter_actor_iter_init (&iter, CLUTTER_ACTOR (container)); while (clutter_actor_iter_next (&iter, &child)) { float child_minimum, child_natural; if (!CLUTTER_ACTOR_IS_VISIBLE (child)) continue; clutter_actor_get_preferred_height (child, -1.f, &child_minimum, &child_natural); minimum = MAX (minimum, child_minimum); natural = MAX (natural, child_natural); n_children += 1; } self->cell_height = natural; minimum += (self->spacing * (n_children - 1)); natural += (self->spacing * (n_children - 1)); if (min_height_p != NULL) *min_height_p = minimum; if (nat_height_p != NULL) *nat_height_p = natural; } static int get_items_per_row (MultiLayout *self, float for_width) { int n_columns; if (for_width < 0) return 1; if (self->cell_width <= 0) return 1; n_columns = (int) ((for_width + self->spacing) / (self->cell_width + self->spacing)); return MAX (n_columns, 1); } static int get_visible_children (ClutterActor *actor) { ClutterActorIter iter; ClutterActor *child; int n_visible_children = 0; clutter_actor_iter_init (&iter, actor); while (clutter_actor_iter_next (&iter, &child)) { if (CLUTTER_ACTOR_IS_VISIBLE (child)) n_visible_children += 1; } return n_visible_children; } static void multi_layout_allocate (ClutterLayoutManager *manager, ClutterContainer *container, const ClutterActorBox *allocation, ClutterAllocationFlags flags) { MultiLayout *self = (MultiLayout *) manager; float avail_width, avail_height; float x_offset, y_offset; ClutterActorIter iter; ClutterActor *child; float item_x, item_y; int n_items, n_items_per_row, item_index; ClutterPoint center; double radius, theta; gboolean use_animations; ClutterAnimationMode easing_mode; guint easing_duration, easing_delay; n_items = get_visible_children (CLUTTER_ACTOR (container)); if (n_items == 0) return; clutter_actor_box_get_origin (allocation, &x_offset, &y_offset); clutter_actor_box_get_size (allocation, &avail_width, &avail_height); /* ensure we have an updated value of cell_width and cell_height */ multi_layout_get_preferred_width (manager, container, avail_width, NULL, NULL); multi_layout_get_preferred_height (manager, container, avail_height, NULL, NULL); item_index = 0; if (self->state == MULTI_LAYOUT_GRID) { n_items_per_row = get_items_per_row (self, avail_width); item_x = x_offset; item_y = y_offset; } else if (self->state == MULTI_LAYOUT_CIRCLE) { center.x = allocation->x2 / 2.f; center.y = allocation->y2 / 2.f; radius = MIN ((avail_width - self->cell_width) / 2.0, (avail_height - self->cell_height) / 2.0); } use_animations = clutter_layout_manager_get_easing_state (manager, &easing_mode, &easing_duration, &easing_delay); clutter_actor_iter_init (&iter, CLUTTER_ACTOR (container)); while (clutter_actor_iter_next (&iter, &child)) { ClutterActorBox child_allocation = CLUTTER_ACTOR_BOX_INIT (0, 0, 0, 0); if (!CLUTTER_ACTOR_IS_VISIBLE (child)) continue; if (self->state == MULTI_LAYOUT_GRID) { if (item_index == n_items_per_row) { item_index = 0; item_x = x_offset; item_y += self->cell_height + self->spacing; } child_allocation.x1 = item_x; child_allocation.y1 = item_y; child_allocation.x2 = child_allocation.x1 + self->cell_width; child_allocation.y2 = child_allocation.y1 + self->cell_height; item_x += self->cell_width + self->spacing; } else if (self->state == MULTI_LAYOUT_CIRCLE) { theta = 2.0 * G_PI / n_items * item_index; child_allocation.x1 = center.x + radius * sinf (theta) - (self->cell_width / 2.f); child_allocation.y1 = center.y + radius * -cosf (theta) - (self->cell_height / 2.f); child_allocation.x2 = child_allocation.x1 + self->cell_width; child_allocation.y2 = child_allocation.y1 + self->cell_height; } if (use_animations) { clutter_actor_save_easing_state (child); clutter_actor_set_easing_mode (child, easing_mode); clutter_actor_set_easing_duration (child, easing_duration); clutter_actor_set_easing_delay (child, easing_delay); } clutter_actor_allocate (child, &child_allocation, flags); if (use_animations) clutter_actor_restore_easing_state (child); item_index += 1; } } static void multi_layout_class_init (MultiLayoutClass *klass) { ClutterLayoutManagerClass *manager_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass); manager_class->get_preferred_width = multi_layout_get_preferred_width; manager_class->get_preferred_height = multi_layout_get_preferred_height; manager_class->allocate = multi_layout_allocate; } static void multi_layout_init (MultiLayout *self) { self->state = MULTI_LAYOUT_GRID; self->cell_width = -1.f; self->cell_height = -1.f; self->spacing = 0.f; } ClutterLayoutManager * multi_layout_new (void) { return g_object_new (multi_layout_get_type (), NULL); } void multi_layout_set_state (MultiLayout *self, MultiLayoutState state) { if (self->state == state) return; self->state = state; clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (self)); } void multi_layout_set_spacing (MultiLayout *self, float spacing) { self->spacing = spacing; clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (self)); } #define N_RECTS 16 #define RECT_SIZE 64.0 #define N_ROWS 4 #define PADDING 12.0 #define BOX_SIZE (RECT_SIZE * (N_RECTS / N_ROWS) + PADDING * (N_RECTS / N_ROWS - 1)) static gboolean on_key_press (ClutterActor *stage, ClutterEvent *event, ClutterActor *box) { guint keysym = clutter_event_get_key_symbol (event); MultiLayout *layout = (MultiLayout *) clutter_actor_get_layout_manager (box); if (keysym == CLUTTER_KEY_q) { clutter_main_quit (); return CLUTTER_EVENT_STOP; } switch (keysym) { case CLUTTER_KEY_g: multi_layout_set_state (layout, MULTI_LAYOUT_GRID); break; case CLUTTER_KEY_c: multi_layout_set_state (layout, MULTI_LAYOUT_CIRCLE); break; default: break; } return CLUTTER_EVENT_STOP; } int main (int argc, char *argv[]) { ClutterActor *stage, *box, *label; ClutterLayoutManager *manager; ClutterMargin margin; int i; if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS) return EXIT_FAILURE; stage = clutter_stage_new (); clutter_stage_set_title (CLUTTER_STAGE (stage), "Multi-layout"); g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL); clutter_actor_show (stage); manager = multi_layout_new (); multi_layout_set_spacing ((MultiLayout *) manager, PADDING); clutter_layout_manager_set_use_animations (manager, TRUE); margin.top = margin.bottom = margin.left = margin.right = PADDING; box = clutter_actor_new (); clutter_actor_set_margin (box, &margin); clutter_actor_set_layout_manager (box, manager); clutter_actor_set_size (box, BOX_SIZE, BOX_SIZE); clutter_actor_add_constraint (box, clutter_align_constraint_new (stage, CLUTTER_ALIGN_BOTH, 0.5)); clutter_actor_add_child (stage, box); for (i = 0; i < N_RECTS; i++) { ClutterActor *rect = clutter_actor_new (); ClutterColor color; clutter_color_from_hls (&color, 360.0 / N_RECTS * i, 0.5, 0.8); color.alpha = 128 + 128 / N_RECTS * i; clutter_actor_set_size (rect, RECT_SIZE, RECT_SIZE); clutter_actor_set_background_color (rect, &color); clutter_actor_add_child (box, rect); } label = clutter_text_new (); clutter_text_set_text (CLUTTER_TEXT (label), "Press g\t\342\236\236\tGrid layout\n" "Press c\t\342\236\236\tCircular layout\n" "Press q\t\342\236\236\tQuit"); clutter_actor_add_constraint (label, clutter_align_constraint_new (stage, CLUTTER_ALIGN_X_AXIS, 0.5)); clutter_actor_add_constraint (label, clutter_align_constraint_new (stage, CLUTTER_ALIGN_Y_AXIS, 0.95)); clutter_actor_add_child (stage, label); g_signal_connect (stage, "key-press-event", G_CALLBACK (on_key_press), box); clutter_main (); return EXIT_SUCCESS; }