static void st_container_remove (ClutterContainer *container, ClutterActor *actor) { StContainerPrivate *priv = ST_CONTAINER (container)->priv; g_object_ref (actor); priv->children = g_list_remove (priv->children, actor); clutter_actor_unparent (actor); /* queue a relayout, to get the correct positioning inside * the ::actor-removed signal handlers */ clutter_actor_queue_relayout (CLUTTER_ACTOR (container)); /* at this point, the actor passed to the "actor-removed" signal * handlers is not parented anymore to the container but since we * are holding a reference on it, it's still valid */ g_signal_emit_by_name (container, "actor-removed", actor); st_container_update_pseudo_classes (ST_CONTAINER (container)); if (CLUTTER_ACTOR_IS_VISIBLE (container)) clutter_actor_queue_redraw (CLUTTER_ACTOR (container)); g_object_unref (actor); }
static gboolean st_container_get_paint_volume (ClutterActor *actor, ClutterPaintVolume *volume) { StContainerPrivate *priv = ST_CONTAINER (actor)->priv; GList *l; if (!CLUTTER_ACTOR_CLASS (st_container_parent_class)->get_paint_volume (actor, volume)) return FALSE; if (!clutter_actor_get_clip_to_allocation (actor)) { /* Based on ClutterGroup/ClutterBox; include the children's * paint volumes, since they may paint outside our allocation. */ for (l = priv->children; l != NULL; l = l->next) { ClutterActor *child = l->data; const ClutterPaintVolume *child_volume; child_volume = clutter_actor_get_transformed_paint_volume (child, actor); if (!child_volume) return FALSE; clutter_paint_volume_union (volume, child_volume); } } return TRUE; }
static gboolean sagarmatha_stack_navigate_focus (StWidget *widget, ClutterActor *from, GtkDirectionType direction) { ClutterActor *top_actor; GList *children; /* If the stack is itself focusable, then focus into or out of * it, as appropriate. */ if (st_widget_get_can_focus (widget)) { if (from && clutter_actor_contains (CLUTTER_ACTOR (widget), from)) return FALSE; clutter_actor_grab_key_focus (CLUTTER_ACTOR (widget)); return TRUE; } /* Otherwise, navigate into its top-most child only */ children = st_container_get_children_list (ST_CONTAINER (widget)); if (!children) return FALSE; top_actor = g_list_last (children)->data; if (ST_IS_WIDGET (top_actor)) return st_widget_navigate_focus (ST_WIDGET (top_actor), from, direction, FALSE); else return FALSE; }
static void st_container_raise (ClutterContainer *container, ClutterActor *actor, ClutterActor *sibling) { StContainerPrivate *priv = ST_CONTAINER (container)->priv; priv->children = g_list_remove (priv->children, actor); /* Raise at the top */ if (!sibling) { GList *last_item; last_item = g_list_last (priv->children); if (last_item) sibling = last_item->data; priv->children = g_list_append (priv->children, actor); } else { gint pos; pos = g_list_index (priv->children, sibling) + 1; priv->children = g_list_insert (priv->children, actor, pos); } /* set Z ordering a value below, this will then call sort * as values are equal ordering shouldn't change but Z * values will be correct. * * FIXME: optimise */ if (sibling && clutter_actor_get_depth (sibling) != clutter_actor_get_depth (actor)) { clutter_actor_set_depth (actor, clutter_actor_get_depth (sibling)); } st_container_update_pseudo_classes (ST_CONTAINER (container)); if (CLUTTER_ACTOR_IS_VISIBLE (container)) clutter_actor_queue_redraw (CLUTTER_ACTOR (container)); }
/** * st_box_layout_insert_actor: * @self: A #StBoxLayout * @actor: A #ClutterActor * @pos: position to insert actor * * Adds @actor to @self at position @pos. If @pos is * negative or larger than the number of elements in the * list then @actor is added after all the others previously * added. */ void st_box_layout_insert_actor (StBoxLayout *self, ClutterActor *actor, int pos) { clutter_container_add_actor((ClutterContainer*) self, actor); st_container_move_child (ST_CONTAINER (self), actor, pos); }
static void st_box_layout_paint (ClutterActor *actor) { StBoxLayout *self = ST_BOX_LAYOUT (actor); StBoxLayoutPrivate *priv = self->priv; StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); GList *l, *children; gdouble x, y; ClutterActorBox allocation_box; ClutterActorBox content_box; get_border_paint_offsets (self, &x, &y); if (x != 0 || y != 0) { cogl_push_matrix (); cogl_translate ((int)x, (int)y, 0); } CLUTTER_ACTOR_CLASS (st_box_layout_parent_class)->paint (actor); if (x != 0 || y != 0) { cogl_pop_matrix (); } children = st_container_get_children_list (ST_CONTAINER (actor)); if (children == NULL) return; clutter_actor_get_allocation_box (actor, &allocation_box); st_theme_node_get_content_box (theme_node, &allocation_box, &content_box); content_box.x1 += x; content_box.y1 += y; content_box.x2 += x; content_box.y2 += y; /* The content area forms the viewport into the scrolled contents, while * the borders and background stay in place; after drawing the borders and * background, we clip to the content area */ if (priv->hadjustment || priv->vadjustment) cogl_clip_push_rectangle ((int)content_box.x1, (int)content_box.y1, (int)content_box.x2, (int)content_box.y2); for (l = children; l; l = g_list_next (l)) { ClutterActor *child = (ClutterActor*) l->data; if (CLUTTER_ACTOR_IS_VISIBLE (child)) clutter_actor_paint (child); } if (priv->hadjustment || priv->vadjustment) cogl_clip_pop (); }
/** * st_box_layout_insert_before: * @self: A #StBoxLayout * @actor: A #ClutterActor * @sibling: A previously added #ClutterActor * * Adds @actor to @self at the position before @sibling. * @sibling cannot be %NULL and must be already a child * of @self. */ void st_box_layout_insert_before (StBoxLayout *self, ClutterActor *actor, ClutterActor *sibling) { g_return_if_fail (ST_IS_BOX_LAYOUT (self)); clutter_container_add_actor(CLUTTER_CONTAINER (self), actor); st_container_move_before (ST_CONTAINER (self), actor, sibling); }
static void st_container_sort_depth_order (ClutterContainer *container) { StContainerPrivate *priv = ST_CONTAINER (container)->priv; priv->children = g_list_sort (priv->children, sort_z_order); if (CLUTTER_ACTOR_IS_VISIBLE (container)) clutter_actor_queue_redraw (CLUTTER_ACTOR (container)); }
static void st_container_lower (ClutterContainer *container, ClutterActor *actor, ClutterActor *sibling) { StContainerPrivate *priv = ST_CONTAINER (container)->priv; priv->children = g_list_remove (priv->children, actor); /* Push to bottom */ if (!sibling) { GList *last_item; last_item = g_list_first (priv->children); if (last_item) sibling = last_item->data; priv->children = g_list_prepend (priv->children, actor); } else { gint pos; pos = g_list_index (priv->children, sibling); priv->children = g_list_insert (priv->children, actor, pos); } /* See comment in st_container_raise() for this */ if (sibling && clutter_actor_get_depth (sibling) != clutter_actor_get_depth (actor)) { clutter_actor_set_depth (actor, clutter_actor_get_depth (sibling)); } st_container_update_pseudo_classes (ST_CONTAINER (container)); if (CLUTTER_ACTOR_IS_VISIBLE (container)) clutter_actor_queue_redraw (CLUTTER_ACTOR (container)); }
static void st_table_hide_all (ClutterActor *table) { GList *l, *children; clutter_actor_hide (table); children = st_container_get_children_list (ST_CONTAINER (table)); for (l = children; l; l = l->next) clutter_actor_hide_all (CLUTTER_ACTOR (l->data)); }
static void sagarmatha_stack_get_preferred_height (ClutterActor *actor, gfloat for_width, gfloat *min_height_p, gfloat *natural_height_p) { SagarmathaStack *stack = SAGARMATHA_STACK (actor); StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); gboolean first = TRUE; float min = 0, natural = 0; GList *children; GList *iter; st_theme_node_adjust_for_width (theme_node, &for_width); children = st_container_get_children_list (ST_CONTAINER (stack)); for (iter = children; iter; iter = iter->next) { ClutterActor *child = iter->data; float child_min, child_natural; clutter_actor_get_preferred_height (child, for_width, &child_min, &child_natural); if (first) { first = FALSE; min = child_min; natural = child_natural; } else { if (child_min > min) min = child_min; if (child_natural > natural) natural = child_natural; } } if (min_height_p) *min_height_p = min; if (natural_height_p) *natural_height_p = natural; st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p); }
static void st_container_add (ClutterContainer *container, ClutterActor *actor) { StContainerPrivate *priv = ST_CONTAINER (container)->priv; g_object_ref (actor); priv->children = g_list_append (priv->children, actor); clutter_actor_add_child (CLUTTER_ACTOR (container), actor); /* queue a relayout, to get the correct positioning inside * the ::actor-added signal handlers */ clutter_actor_queue_relayout (CLUTTER_ACTOR (container)); g_signal_emit_by_name (container, "actor-added", actor); clutter_container_sort_depth_order (container); st_container_update_pseudo_classes (ST_CONTAINER (container)); g_object_unref (actor); }
static void st_container_foreach (ClutterContainer *container, ClutterCallback callback, gpointer user_data) { StContainerPrivate *priv = ST_CONTAINER (container)->priv; /* Using g_list_foreach instead of iterating the list manually * because it has better protection against the current node being * removed. This will happen for example if someone calls * clutter_container_foreach(container, clutter_actor_destroy) */ g_list_foreach (priv->children, (GFunc) callback, user_data); }
static void sagarmatha_stack_allocate (ClutterActor *self, const ClutterActorBox *box, ClutterAllocationFlags flags) { StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self)); ClutterActorBox content_box; GList *children, *iter; CLUTTER_ACTOR_CLASS (sagarmatha_stack_parent_class)->allocate (self, box, flags); st_theme_node_get_content_box (theme_node, box, &content_box); children = st_container_get_children_list (ST_CONTAINER (self)); for (iter = children; iter; iter = iter->next) { ClutterActor *actor = CLUTTER_ACTOR (iter->data); ClutterActorBox child_box = content_box; clutter_actor_allocate (actor, &child_box, flags); } }
/* * ClutterContainer Implementation */ static void st_table_actor_removed (ClutterContainer *container, ClutterActor *actor) { StTablePrivate *priv = ST_TABLE (container)->priv; GList *list, *children; gint n_rows = 0; gint n_cols = 0; /* Calculate and update the number of rows / columns */ children = st_container_get_children_list (ST_CONTAINER (container)); for (list = children; list; list = list->next) { ClutterActor *child = CLUTTER_ACTOR (list->data); StTableChild *meta; if (child == actor) continue; meta = (StTableChild *) clutter_container_get_child_meta (container, child); n_rows = MAX (n_rows, meta->row + 1); n_cols = MAX (n_cols, meta->col + 1); } g_object_freeze_notify (G_OBJECT (container)); if (priv->n_rows != n_rows) { priv->n_rows = n_rows; g_object_notify (G_OBJECT (container), "row-count"); } if (priv->n_cols != n_cols) { priv->n_cols = n_cols; g_object_notify (G_OBJECT (container), "column-count"); } g_object_thaw_notify (G_OBJECT (container)); }
static void st_container_dispose (GObject *object) { StContainerPrivate *priv = ST_CONTAINER (object)->priv; if (priv->children) { clutter_actor_destroy_all_children (CLUTTER_ACTOR (object)); g_list_free (priv->children); priv->children = NULL; } if (priv->first_child) g_object_unref (priv->first_child); priv->first_child = NULL; if (priv->last_child) g_object_unref (priv->last_child); priv->last_child = NULL; G_OBJECT_CLASS (st_container_parent_class)->dispose (object); }
static gboolean st_container_navigate_focus (StWidget *widget, ClutterActor *from, GtkDirectionType direction) { StContainer *container = ST_CONTAINER (widget); ClutterActor *container_actor, *focus_child; GList *children, *l; container_actor = CLUTTER_ACTOR (widget); if (from == container_actor) return FALSE; /* Figure out if @from is a descendant of @container, and if so, * set @focus_child to the immediate child of @container that * contains (or *is*) @from. */ focus_child = from; while (focus_child && clutter_actor_get_parent (focus_child) != container_actor) focus_child = clutter_actor_get_parent (focus_child); if (st_widget_get_can_focus (widget)) { if (!focus_child) { /* Accept focus from outside */ clutter_actor_grab_key_focus (container_actor); return TRUE; } else { /* Yield focus from within: since @container itself is * focusable we don't allow the focus to be navigated * within @container. */ return FALSE; } } /* See if we can navigate within @focus_child */ if (focus_child && ST_IS_WIDGET (focus_child)) { if (st_widget_navigate_focus (ST_WIDGET (focus_child), from, direction, FALSE)) return TRUE; } /* At this point we know that we want to navigate focus to one of * @container's immediate children; the next one after @focus_child, * or the first one if @focus_child is %NULL. (With "next" and * "first" being determined by @direction.) */ children = st_container_get_focus_chain (container); if (direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD) { if (direction == GTK_DIR_TAB_BACKWARD) children = g_list_reverse (children); if (focus_child) { /* Remove focus_child and any earlier children */ while (children && children->data != focus_child) children = g_list_delete_link (children, children); if (children) children = g_list_delete_link (children, children); } } else /* direction is an arrow key, not tab */ { StContainerChildSortData sort_data; /* Compute the allocation box of the previous focused actor, in * @container's coordinate space. If there was no previous focus, * use the coordinates of the appropriate edge of @container. * * Note that all of this code assumes the actors are not * transformed (or at most, they are all scaled by the same * amount). If @container or any of its children is rotated, or * any child is inconsistently scaled, then the focus chain will * probably be unpredictable. */ if (focus_child) { clutter_actor_get_allocation_box (focus_child, &sort_data.box); } else { clutter_actor_get_allocation_box (CLUTTER_ACTOR (container), &sort_data.box); switch (direction) { case GTK_DIR_UP: sort_data.box.y1 = sort_data.box.y2; break; case GTK_DIR_DOWN: sort_data.box.y2 = sort_data.box.y1; break; case GTK_DIR_LEFT: sort_data.box.x1 = sort_data.box.x2; break; case GTK_DIR_RIGHT: sort_data.box.x2 = sort_data.box.x1; break; default: g_warn_if_reached (); } } sort_data.direction = direction; if (focus_child) children = filter_by_position (children, &sort_data.box, direction); if (children) children = g_list_sort_with_data (children, sort_by_position, &sort_data); } /* Now try each child in turn */ for (l = children; l; l = l->next) { if (ST_IS_WIDGET (l->data)) { if (st_widget_navigate_focus (l->data, from, direction, FALSE)) { g_list_free (children); return TRUE; } } } g_list_free (children); return FALSE; }
static void st_table_homogeneous_allocate (ClutterActor *self, const ClutterActorBox *content_box, gboolean flags) { GList *list, *children; gfloat col_width, row_height; gint row_spacing, col_spacing; StTablePrivate *priv = ST_TABLE (self)->priv; gboolean ltr = st_widget_get_direction (ST_WIDGET (self)) == ST_TEXT_DIRECTION_LTR; col_spacing = priv->col_spacing; row_spacing = priv->row_spacing; col_width = (int) ((content_box->x2 - content_box->x1 - (col_spacing * (priv->n_cols - 1))) / priv->n_cols + 0.5); row_height = (int) ((content_box->y2 - content_box->y1 - (row_spacing * (priv->n_rows - 1))) / priv->n_rows + 0.5); children = st_container_get_children_list (ST_CONTAINER (self)); for (list = children; list; list = list->next) { gint row, col, row_span, col_span; StTableChild *meta; ClutterActor *child; ClutterActorBox childbox; StAlign x_align, y_align; gboolean x_fill, y_fill; child = CLUTTER_ACTOR (list->data); meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (self), child); if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child)) continue; /* get child properties */ col = meta->col; row = meta->row; row_span = meta->row_span; col_span = meta->col_span; x_align = meta->x_align; y_align = meta->y_align; x_fill = meta->x_fill; y_fill = meta->y_fill; if (ltr) { childbox.x1 = content_box->x1 + (col_width + col_spacing) * col; childbox.x2 = childbox.x1 + (col_width * col_span) + (col_spacing * (col_span - 1)); } else { childbox.x2 = content_box->x2 - (col_width + col_spacing) * col; childbox.x1 = childbox.x2 - (col_width * col_span) - (col_spacing * (col_span - 1)); } childbox.y1 = content_box->y1 + (row_height + row_spacing) * row; childbox.y2 = childbox.y1 + (row_height * row_span) + (row_spacing * (row_span - 1)); _st_allocate_fill (ST_WIDGET (self), child, &childbox, x_align, y_align, x_fill, y_fill); clutter_actor_allocate (child, &childbox, flags); } }
static void st_table_calculate_col_widths (StTable *table, gint for_width) { StTablePrivate *priv = table->priv; GList *list, *children; gint i; DimensionData *columns; g_array_set_size (priv->columns, 0); g_array_set_size (priv->columns, priv->n_cols); columns = &g_array_index (priv->columns, DimensionData, 0); /* Reset all the visible attributes for the columns */ priv->visible_cols = 0; for (i = 0; i < priv->n_cols; i++) columns[i].is_visible = FALSE; children = st_container_get_children_list (ST_CONTAINER (table)); /* STAGE ONE: calculate column widths for non-spanned children */ for (list = children; list; list = list->next) { StTableChild *meta; ClutterActor *child; DimensionData *col; gfloat w_min, w_pref; child = CLUTTER_ACTOR (list->data); meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (table), child); if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child)) continue; if (meta->col_span > 1) continue; col = &columns[meta->col]; /* If this child is visible, then its column is visible */ if (!col->is_visible) { col->is_visible = TRUE; priv->visible_cols++; } _st_actor_get_preferred_width (child, -1, meta->y_fill, &w_min, &w_pref); col->min_size = MAX (col->min_size, w_min); col->final_size = col->pref_size = MAX (col->pref_size, w_pref); col->expand = MAX (col->expand, meta->x_expand); } /* STAGE TWO: take spanning children into account */ for (list = children; list; list = list->next) { StTableChild *meta; ClutterActor *child; gfloat w_min, w_pref; gfloat min_width, pref_width; gint start_col, end_col; gint n_expand; child = CLUTTER_ACTOR (list->data); meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (table), child); if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child)) continue; if (meta->col_span < 2) continue; start_col = meta->col; end_col = meta->col + meta->col_span - 1; _st_actor_get_preferred_width (child, -1, meta->y_fill, &w_min, &w_pref); /* check there is enough room for this actor */ min_width = 0; pref_width = 0; n_expand = 0; for (i = start_col; i <= end_col; i++) { min_width += columns[i].min_size; pref_width += columns[i].pref_size; if (columns[i].expand) { n_expand++; } /* If this child is visible, then the columns it spans are also visible */ if (!columns[i].is_visible) { columns[i].is_visible = TRUE; priv->visible_cols++; } columns[i].expand = MAX (columns[i].expand, meta->x_expand); } min_width += priv->col_spacing * (meta->col_span - 1); pref_width += priv->col_spacing * (meta->col_span - 1); /* see st_table_calculate_row_heights() for comments */ /* (1) */ if (w_min > min_width) { /* (2) */ /* we can start from preferred width and decrease */ if (pref_width > w_min) { for (i = start_col; i <= end_col; i++) { columns[i].final_size = columns[i].pref_size; } while (pref_width > w_min) { for (i = start_col; i <= end_col; i++) { if (columns[i].final_size > columns[i].min_size) { columns[i].final_size--; pref_width--; } } } for (i = start_col; i <= end_col; i++) { columns[i].min_size = columns[i].final_size; } } else { /* (3) */ /* we can expand from preferred size */ gfloat expand_by; expand_by = w_pref - pref_width; for (i = start_col; i <= end_col; i++) { if (n_expand) { if (columns[i].expand) columns[i].min_size = columns[i].pref_size + expand_by / n_expand; } else { columns[i].min_size = columns[i].pref_size + expand_by / meta->col_span; } } } } } /* calculate final widths */ if (for_width >= 0) { gfloat min_width, pref_width; gint n_expand; min_width = 0; pref_width = 0; n_expand = 0; for (i = 0; i < priv->n_cols; i++) { pref_width += columns[i].pref_size; min_width += columns[i].min_size; if (columns[i].expand) n_expand++; } pref_width += priv->col_spacing * (priv->n_cols - 1); min_width += priv->col_spacing * (priv->n_cols - 1); if (for_width <= min_width) { /* erk, we can't shrink this! */ for (i = 0; i < priv->n_cols; i++) { columns[i].final_size = columns[i].min_size; } return; } if (for_width == pref_width) { /* perfect! */ for (i = 0; i < priv->n_cols; i++) { columns[i].final_size = columns[i].pref_size; } return; } /* for_width is between min_width and pref_width */ if (for_width < pref_width && for_width > min_width) { gfloat width; /* shrink columns until they reach min_width */ /* start with all columns at preferred size */ for (i = 0; i < priv->n_cols; i++) { columns[i].final_size = columns[i].pref_size; } width = pref_width; while (width > for_width) { for (i = 0; i < priv->n_cols; i++) { if (columns[i].final_size > columns[i].min_size) { columns[i].final_size--; width--; } } } return; } /* expand columns */ if (for_width > pref_width) { gfloat extra_width = for_width - pref_width; gint remaining; if (n_expand) remaining = (gint) extra_width % n_expand; else remaining = (gint) extra_width % priv->n_cols; for (i = 0; i < priv->n_cols; i++) { if (columns[i].expand) columns[i].final_size = columns[i].pref_size + (extra_width / n_expand); else if (!n_expand) columns[i].final_size = columns[i].pref_size + (extra_width / priv->n_cols); else columns[i].final_size = columns[i].pref_size; } /* distribute the remainder among children */ i = 0; while (remaining) { if (columns[i].expand || !n_expand) { columns[i].final_size++; remaining--; } i++; } } } }
static void st_table_calculate_row_heights (StTable *table, gint for_height) { StTablePrivate *priv = ST_TABLE (table)->priv; GList *list, *children; gint i; DimensionData *rows, *columns; g_array_set_size (priv->rows, 0); g_array_set_size (priv->rows, priv->n_rows); rows = &g_array_index (priv->rows, DimensionData, 0); columns = &g_array_index (priv->columns, DimensionData, 0); /* Reset the visible rows */ priv->visible_rows = 0; for (i = 0; i < priv->n_rows; i++) rows[i].is_visible = FALSE; /* STAGE ONE: calculate row heights for non-spanned children */ children = st_container_get_children_list (ST_CONTAINER (table)); for (list = children; list; list = list->next) { StTableChild *meta; ClutterActor *child; DimensionData *row; gfloat c_min, c_pref; child = CLUTTER_ACTOR (list->data); meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (table), child); if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child)) continue; if (meta->row_span > 1) continue; row = &rows[meta->row]; /* If this child is visible, then its row is visible */ if (!row->is_visible) { row->is_visible = TRUE; priv->visible_rows++; } _st_actor_get_preferred_height (child, columns[meta->col].final_size, meta->x_fill, &c_min, &c_pref); row->min_size = MAX (row->min_size, c_min); row->final_size = row->pref_size = MAX (row->pref_size, c_pref); row->expand = MAX (row->expand, meta->y_expand); } /* STAGE TWO: take spanning children into account */ for (list = children; list; list = list->next) { StTableChild *meta; ClutterActor *child; gfloat c_min, c_pref; gfloat min_height, pref_height; gint start_row, end_row; gint n_expand; child = CLUTTER_ACTOR (list->data); meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (table), child); if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child)) continue; if (meta->row_span < 2) continue; start_row = meta->row; end_row = meta->row + meta->row_span - 1; _st_actor_get_preferred_height (child, columns[meta->col].final_size, meta->x_fill, &c_min, &c_pref); /* check there is enough room for this actor */ min_height = 0; pref_height = 0; n_expand = 0; for (i = start_row; i <= end_row; i++) { min_height += rows[i].min_size; pref_height += rows[i].pref_size; if (rows[i].expand) { n_expand++; } /* If this actor is visible, then all the rows is spans are visible */ if (!rows[i].is_visible) { rows[i].is_visible = TRUE; priv->visible_rows++; } rows[i].expand = MAX (rows[i].expand, meta->y_expand); } min_height += priv->row_spacing * (meta->row_span - 1); pref_height += priv->row_spacing * (meta->row_span - 1); /* 1) If the minimum height of the rows spanned is less than the minimum * height of the child that is spanning them, then we must increase the * minimum height of the rows spanned. * * 2) If the preferred height of the spanned rows is more that the minimum * height of the spanning child, then we can start at this size and * decrease each row evenly. * * 3) If the preferred height of the rows is more than the minimum height * of the spanned child, then we can start at the preferred height and * expand. */ /* (1) */ if (c_min > min_height) { /* (2) */ /* we can start from preferred height and decrease */ if (pref_height > c_min) { for (i = start_row; i <= end_row; i++) { rows[i].final_size = rows[i].pref_size; } while (pref_height > c_min) { for (i = start_row; i <= end_row; i++) { if (rows[i].final_size > rows[i].min_size) { rows[i].final_size--; pref_height--; } } } for (i = start_row; i <= end_row; i++) { rows[i].min_size = rows[i].final_size; } } else { /* (3) */ /* we can expand from preferred size */ gfloat expand_by; expand_by = c_pref - pref_height; for (i = start_row; i <= end_row; i++) { if (n_expand) { if (rows[i].expand) rows[i].min_size = rows[i].pref_size + expand_by / n_expand; } else { rows[i].min_size = rows[i].pref_size + expand_by / meta->row_span; } } } } } /* calculate final heights */ if (for_height >= 0) { gfloat min_height, pref_height; gint n_expand; min_height = 0; pref_height = 0; n_expand = 0; for (i = 0; i < priv->n_rows; i++) { pref_height += rows[i].pref_size; min_height += rows[i].min_size; if (rows[i].expand) n_expand++; } pref_height += priv->row_spacing * (priv->n_rows - 1); min_height += priv->row_spacing * (priv->n_rows - 1); if (for_height <= min_height) { /* erk, we can't shrink this! */ for (i = 0; i < priv->n_rows; i++) { rows[i].final_size = rows[i].min_size; } return; } if (for_height == pref_height) { /* perfect! */ for (i = 0; i < priv->n_rows; i++) { rows[i].final_size = rows[i].pref_size; } return; } /* for_height is between min_height and pref_height */ if (for_height < pref_height && for_height > min_height) { gfloat height; /* shrink rows until they reach min_height */ /* start with all rows at preferred size */ for (i = 0; i < priv->n_rows; i++) { rows[i].final_size = rows[i].pref_size; } height = pref_height; while (height > for_height) { for (i = 0; i < priv->n_rows; i++) { if (rows[i].final_size > rows[i].min_size) { rows[i].final_size--; height--; } } } return; } /* expand rows */ if (for_height > pref_height) { gfloat extra_height = for_height - pref_height; gint remaining; if (n_expand) remaining = (gint) extra_height % n_expand; else remaining = (gint) extra_height % priv->n_rows; for (i = 0; i < priv->n_rows; i++) { if (rows[i].expand) rows[i].final_size = rows[i].pref_size + (extra_height / n_expand); else if (!n_expand) rows[i].final_size = rows[i].pref_size + (extra_height / priv->n_rows); else rows[i].final_size = rows[i].pref_size; } /* distribute the remainder among children */ i = 0; while (remaining) { if (rows[i].expand || !n_expand) { rows[i].final_size++; remaining--; } i++; } } } }
static void st_table_preferred_allocate (ClutterActor *self, const ClutterActorBox *content_box, gboolean flags) { GList *list, *children; gint row_spacing, col_spacing; gint i; StTable *table; StTablePrivate *priv; gboolean ltr; DimensionData *rows, *columns; table = ST_TABLE (self); priv = ST_TABLE (self)->priv; col_spacing = (priv->col_spacing); row_spacing = (priv->row_spacing); st_table_calculate_dimensions (table, content_box->x2 - content_box->x1, content_box->y2 - content_box->y1); ltr = (st_widget_get_direction (ST_WIDGET (self)) == ST_TEXT_DIRECTION_LTR); rows = &g_array_index (priv->rows, DimensionData, 0); columns = &g_array_index (priv->columns, DimensionData, 0); children = st_container_get_children_list (ST_CONTAINER (self)); for (list = children; list; list = list->next) { gint row, col, row_span, col_span; gint col_width, row_height; StTableChild *meta; ClutterActor *child; ClutterActorBox childbox; gint child_x, child_y; StAlign x_align, y_align; gboolean x_fill, y_fill; child = CLUTTER_ACTOR (list->data); meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (self), child); if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child)) continue; /* get child properties */ col = meta->col; row = meta->row; row_span = meta->row_span; col_span = meta->col_span; x_align = meta->x_align; y_align = meta->y_align; x_fill = meta->x_fill; y_fill = meta->y_fill; /* initialise the width and height */ col_width = columns[col].final_size; row_height = rows[row].final_size; /* Add the widths of the spanned columns: * * First check that we have a non-zero span. Then we loop over each of * the columns that we're spanning but we stop short if we go past the * number of columns in the table. This is necessary to avoid accessing * uninitialised memory. We add the spacing in here too since we only * want to add as much spacing as times we successfully span. */ if (col + col_span > priv->n_cols) g_warning ("StTable: the child at %d,%d's col-span, %d, exceeds number of columns, %d", col, row, col_span, priv->n_cols); if (row + row_span > priv->n_rows) g_warning ("StTable: the child at %d,%d's row-span, %d, exceeds number of rows, %d", col, row, row_span, priv->n_rows); if (col_span > 1) { for (i = col + 1; i < col + col_span && i < priv->n_cols; i++) { col_width += columns[i].final_size; col_width += col_spacing; } } /* add the height of the spanned rows */ if (row_span > 1) { for (i = row + 1; i < row + row_span && i < priv->n_rows; i++) { row_height += rows[i].final_size; row_height += row_spacing; } } /* calculate child x */ if (ltr) { child_x = (int) content_box->x1 + col_spacing * col; for (i = 0; i < col; i++) child_x += columns[i].final_size; } else { child_x = (int) content_box->x2 - col_spacing * col; for (i = 0; i < col; i++) child_x -= columns[i].final_size; } /* calculate child y */ child_y = (int) content_box->y1 + row_spacing * row; for (i = 0; i < row; i++) child_y += rows[i].final_size; /* set up childbox */ if (ltr) { childbox.x1 = (float) child_x; childbox.x2 = (float) MAX (0, child_x + col_width); } else { childbox.x2 = (float) child_x; childbox.x1 = (float) MAX (0, child_x - col_width); } childbox.y1 = (float) child_y; childbox.y2 = (float) MAX (0, child_y + row_height); _st_allocate_fill (ST_WIDGET (self), child, &childbox, x_align, y_align, x_fill, y_fill); clutter_actor_allocate (child, &childbox, flags); } }
static void get_content_preferred_height (StBoxLayout *self, gfloat for_width, gfloat *min_height_p, gfloat *natural_height_p) { StBoxLayoutPrivate *priv = self->priv; gint n_children = 0; gint n_fixed = 0; gfloat min_height, natural_height; GList *l, *children; min_height = 0; natural_height = 0; children = st_container_get_children_list (ST_CONTAINER (self)); for (l = children; l; l = g_list_next (l)) { ClutterActor *child = l->data; gfloat child_min = 0, child_nat = 0; gboolean child_fill = FALSE; if (!CLUTTER_ACTOR_IS_VISIBLE (child)) continue; n_children++; if (clutter_actor_get_fixed_position_set (child)) { n_fixed++; continue; } if (priv->is_vertical) { clutter_container_child_get ((ClutterContainer*) self, child, "x-fill", &child_fill, NULL); } _st_actor_get_preferred_height (child, (priv->is_vertical) ? for_width : -1, child_fill, &child_min, &child_nat); if (!priv->is_vertical) { min_height = MAX (child_min, min_height); natural_height = MAX (child_nat, natural_height); } else { min_height += child_min; natural_height += child_nat; } } if (priv->is_vertical && (n_children - n_fixed) > 1) { min_height += priv->spacing * (n_children - n_fixed - 1); natural_height += priv->spacing * (n_children - n_fixed - 1); } if (min_height_p) *min_height_p = min_height; if (natural_height_p) *natural_height_p = natural_height; }
static BoxChildShrink * compute_shrinks (StBoxLayout *self, gfloat for_length, gfloat total_shrink) { StBoxLayoutPrivate *priv = self->priv; GList *children = st_container_get_children_list (ST_CONTAINER (self)); int n_children = g_list_length (children); BoxChildShrink *shrinks = g_new0 (BoxChildShrink, n_children); gfloat shrink_so_far; gfloat base_shrink = 0; /* the "= 0" is just to make gcc happy */ int n_shrink_children; GList *l; int i; /* The effect that we want is that all the children get an equal chance * to expand from their minimum size up to the natural size. Or to put * it a different way, we want to start by shrinking only the child that * can shrink most, then shrink that and the next most shrinkable child, * to the point where we are shrinking everything. */ /* Find the amount of possible shrink for each child */ int n_possible_shrink_children = 0; for (l = children, i = 0; l; l = l->next, i++) { ClutterActor *child; gfloat child_min, child_nat; gboolean child_fill; gboolean fixed; child = (ClutterActor*) l->data; fixed = clutter_actor_get_fixed_position_set (child); shrinks[i].child_index = i; if (CLUTTER_ACTOR_IS_VISIBLE (child) && !fixed) { if (priv->is_vertical) { clutter_container_child_get ((ClutterContainer*) self, child, "x-fill", &child_fill, NULL); _st_actor_get_preferred_height (child, for_length, child_fill, &child_min, &child_nat); } else { clutter_container_child_get ((ClutterContainer*) self, child, "y-fill", &child_fill, NULL); _st_actor_get_preferred_width (child, for_length, child_fill, &child_min, &child_nat); } shrinks[i].shrink_amount = MAX (0., child_nat - child_min); n_possible_shrink_children++; } else { shrinks[i].shrink_amount = -1.; } } /* We want to process children starting from the child with the maximum available * shrink, so sort in this order; !visible children end up at the end */ qsort (shrinks, n_children, sizeof (BoxChildShrink), compare_by_shrink_amount); /* +--+ * | | * | | +-- * | | | | * | | | | +-+ * --+--+-+-+-+-+---------- * | | | | | | +-+ +-+ * | | | | | | | | | | * --+--+-+-+-+-+-+-+------ * * We are trying to find the correct position for the upper line the "water mark" * so that total of the portion of the bars above the line is equal to the total * amount we want to shrink. */ /* Start by moving the line downward, top-of-bar by top-of-bar */ shrink_so_far = 0; for (n_shrink_children = 1; n_shrink_children <= n_possible_shrink_children; n_shrink_children++) { if (n_shrink_children < n_possible_shrink_children) base_shrink = shrinks[n_shrink_children].shrink_amount; else base_shrink = 0; shrink_so_far += n_shrink_children * (shrinks[n_shrink_children - 1].shrink_amount - base_shrink); if (shrink_so_far >= total_shrink || n_shrink_children == n_possible_shrink_children) break; } /* OK, we found enough shrinkage, move it back upwards to the right position */ base_shrink += (shrink_so_far - total_shrink) / n_shrink_children; if (base_shrink < 0) /* can't shrink that much, probably round-off error */ base_shrink = 0; /* Assign the portion above the base shrink line to the shrink_amount */ for (i = 0; i < n_shrink_children; i++) shrinks[i].shrink_amount -= base_shrink; for (; i < n_children; i++) shrinks[i].shrink_amount = 0; /* And sort back to their original order */ qsort (shrinks, n_children, sizeof (BoxChildShrink), compare_by_child_index); return shrinks; }
static void st_box_layout_allocate (ClutterActor *actor, const ClutterActorBox *box, ClutterAllocationFlags flags) { StBoxLayoutPrivate *priv = ST_BOX_LAYOUT (actor)->priv; StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); ClutterActorBox content_box; gfloat avail_width, avail_height, min_width, natural_width, min_height, natural_height; gfloat position, next_position; GList *l, *children; gint n_expand_children = 0, i; gfloat expand_amount, shrink_amount; BoxChildShrink *shrinks = NULL; gboolean flip = (st_widget_get_direction (ST_WIDGET (actor)) == ST_TEXT_DIRECTION_RTL) && (!priv->is_vertical); CLUTTER_ACTOR_CLASS (st_box_layout_parent_class)->allocate (actor, box, flags); children = st_container_get_children_list (ST_CONTAINER (actor)); if (children == NULL) return; st_theme_node_get_content_box (theme_node, box, &content_box); avail_width = content_box.x2 - content_box.x1; avail_height = content_box.y2 - content_box.y1; get_content_preferred_width (ST_BOX_LAYOUT (actor), avail_height, &min_width, &natural_width); get_content_preferred_height (ST_BOX_LAYOUT (actor), MAX (avail_width, min_width), &min_height, &natural_height); /* update adjustments for scrolling */ if (priv->vadjustment) { gdouble prev_value; g_object_set (G_OBJECT (priv->vadjustment), "lower", 0.0, "upper", MAX (min_height, avail_height), "page-size", avail_height, "step-increment", avail_height / 6, "page-increment", avail_height - avail_height / 6, NULL); prev_value = st_adjustment_get_value (priv->vadjustment); st_adjustment_set_value (priv->vadjustment, prev_value); } if (priv->hadjustment) { gdouble prev_value; g_object_set (G_OBJECT (priv->hadjustment), "lower", 0.0, "upper", MAX (min_width, avail_width), "page-size", avail_width, "step-increment", avail_width / 6, "page-increment", avail_width - avail_width / 6, NULL); prev_value = st_adjustment_get_value (priv->hadjustment); st_adjustment_set_value (priv->hadjustment, prev_value); } if (avail_height < min_height) { avail_height = min_height; content_box.y2 = content_box.y1 + avail_height; } if (avail_width < min_width) { avail_width = min_width; content_box.x2 = content_box.x1 + avail_width; } if (priv->is_vertical) { expand_amount = MAX (0, avail_height - natural_height); shrink_amount = MAX (0, natural_height - avail_height); } else { expand_amount = MAX (0, avail_width - natural_width); shrink_amount = MAX (0, natural_width - avail_width); } if (expand_amount > 0) { /* count the number of children with expand set to TRUE */ n_expand_children = 0; for (l = children; l; l = l->next) { ClutterActor *child = l->data; gboolean expand; if (!CLUTTER_ACTOR_IS_VISIBLE (child) || clutter_actor_get_fixed_position_set (child)) continue; clutter_container_child_get ((ClutterContainer *) actor, child, "expand", &expand, NULL); if (expand) n_expand_children++; } if (n_expand_children == 0) expand_amount = 0; } else if (shrink_amount > 0) { shrinks = compute_shrinks (ST_BOX_LAYOUT (actor), priv->is_vertical ? avail_width : avail_height, shrink_amount); } if (priv->is_vertical) position = content_box.y1; else if (flip) position = content_box.x2; else position = content_box.x1; if (priv->is_pack_start) { l = g_list_last (children); i = g_list_length (children); } else { l = children; i = 0; } gboolean firstchild = TRUE; gfloat init_padding = (avail_width/2) - (natural_width/2); while (l) { ClutterActor *child = (ClutterActor*) l->data; ClutterActorBox child_box; gfloat child_min, child_nat, child_allocated; gboolean xfill, yfill, expand, fixed; StAlign xalign, yalign; if (!CLUTTER_ACTOR_IS_VISIBLE (child)) goto next_child; fixed = clutter_actor_get_fixed_position_set (child); if (fixed) { clutter_actor_allocate_preferred_size (child, flags); goto next_child; } clutter_container_child_get ((ClutterContainer*) actor, child, "x-fill", &xfill, "y-fill", &yfill, "x-align", &xalign, "y-align", &yalign, "expand", &expand, NULL); if (priv->is_vertical) { _st_actor_get_preferred_height (child, avail_width, xfill, &child_min, &child_nat); } else { _st_actor_get_preferred_width (child, avail_height, yfill, &child_min, &child_nat); } child_allocated = child_nat; if (expand_amount > 0 && expand) child_allocated += expand_amount / n_expand_children; else if (shrink_amount > 0) child_allocated -= shrinks[i].shrink_amount; if (flip) { next_position = position - child_allocated; if (xalign == ST_ALIGN_CENTER_SPECIAL && next_position < content_box.x1) next_position = content_box.x1; } else { next_position = position + child_allocated; if (xalign == ST_ALIGN_CENTER_SPECIAL && next_position > content_box.x2) next_position = content_box.x2; } if (priv->is_vertical) { child_box.y1 = (int)(0.5 + position); child_box.y2 = (int)(0.5 + next_position); child_box.x1 = content_box.x1; child_box.x2 = content_box.x2; _st_allocate_fill (ST_WIDGET (actor), child, &child_box, xalign, yalign, xfill, yfill); clutter_actor_allocate (child, &child_box, flags); } else { if (flip) { if (firstchild && xalign == ST_ALIGN_CENTER_SPECIAL) { position -= init_padding; next_position = position - child_allocated; firstchild = FALSE; } if (xalign == ST_ALIGN_CENTER_SPECIAL && position > content_box.x2) { position = content_box.x2; } child_box.x1 = (int)(0.5 + next_position); child_box.x2 = (int)(0.5 + position); } else { if (firstchild && xalign == ST_ALIGN_CENTER_SPECIAL) { position += init_padding; if (position < content_box.x1) { position = content_box.x1; } next_position = position + child_allocated; firstchild = FALSE; } if (xalign == ST_ALIGN_CENTER_SPECIAL && position < content_box.x1) { position = content_box.x1; } child_box.x1 = (int)(0.5 + position); child_box.x2 = (int)(0.5 + next_position); } child_box.y1 = content_box.y1; child_box.y2 = content_box.y2; _st_allocate_fill (ST_WIDGET (actor), child, &child_box, xalign, yalign, xfill, yfill); clutter_actor_allocate (child, &child_box, flags); } if (flip) position = next_position - priv->spacing; else position = next_position + priv->spacing; next_child: if (priv->is_pack_start) { l = l->prev; i--; } else { l = l->next; i++; } } if (shrinks) g_free (shrinks); }