listitemmanager: Change the way listitems are handled

Instead of immediately removing listitems when they are added/removed or
their widgets become unnecessary, force a manual cleanup. Do this
cleanup in size_allocate().

This way, we can assign space to listitems during allocate and be sure
it survives until the next call to size_allocate(), which allows
tracking multiple changes to the listitems in a single frame, as the
assigned space never collapses.

It also simplifies gridview code, because it allows splitting listitems
so that every listitem represents a rectangular region.

Fixes #2971
This commit is contained in:
Benjamin Otte
2021-11-15 15:01:24 +01:00
parent bffa5dfddd
commit 0751a1ab67
5 changed files with 352 additions and 402 deletions

View File

@@ -107,13 +107,13 @@ struct _GtkGridViewClass
struct _Cell
{
GtkListItemManagerItem parent;
guint size; /* total, only counting cells in first column */
GdkRectangle area;
};
struct _CellAugment
{
GtkListItemManagerItemAugment parent;
guint size; /* total, only counting first column */
GdkRectangle bounds;
};
enum
@@ -156,12 +156,12 @@ dump (GtkGridView *self)
if (cell->parent.widget)
n_widgets++;
n_list_rows++;
n_items += cell->parent.n_items;
g_print ("%6u%6u %5ux%3u %s (%upx)\n",
g_print ("%6u%6u %5ux%3u %s (%d, %d, %d, %d)\n",
cell->parent.n_items, n_items,
n_items / (self->n_columns ? self->n_columns : self->min_columns),
n_items % (self->n_columns ? self->n_columns : self->min_columns),
cell->parent.widget ? " (widget)" : "", cell->size);
cell->parent.widget ? " (widget)" : "", cell->area.x, cell->area.y, cell->area.width, cell->area.height);
n_items += cell->parent.n_items;
}
g_print (" => %u widgets in %u list rows\n", n_widgets, n_list_rows);
@@ -179,139 +179,115 @@ cell_augment (GtkRbTree *tree,
gtk_list_item_manager_augment_node (tree, node_augment, node, left, right);
aug->size = cell->size;
aug->bounds = cell->area;
if (left)
{
CellAugment *left_aug = gtk_rb_tree_get_augment (tree, left);
aug->size += left_aug->size;
gdk_rectangle_union (&aug->bounds, &left_aug->bounds, &aug->bounds);
}
if (right)
{
CellAugment *right_aug = gtk_rb_tree_get_augment (tree, right);
aug->size += right_aug->size;
gdk_rectangle_union (&aug->bounds, &right_aug->bounds, &aug->bounds);
}
}
/*<private>
* gtk_grid_view_get_cell_at_y:
* @self: a `GtkGridView`
* @y: an offset in direction of @self's orientation
* @position: (out caller-allocates) (optional): stores the position
* index of the returned row
* @offset: (out caller-allocates) (optional): stores the offset
* in pixels between y and top of cell.
* @offset: (out caller-allocates) (optional): stores the height
* of the cell
*
* Gets the Cell that occupies the leftmost position in the row at offset
* @y into the primary direction.
*
* If y is larger than the height of all cells, %NULL will be returned.
* In particular that means that for an empty grid, %NULL is returned
* for any value.
*
* Returns: (nullable): The first cell at offset y
**/
static Cell *
gtk_grid_view_get_cell_at_y (GtkGridView *self,
int y,
guint *position,
int *offset,
int *size)
static gboolean
cell_get_area (GtkGridView *self,
Cell *cell,
guint pos,
GdkRectangle *area)
{
Cell *cell, *tmp;
guint pos;
int x, y, n;
cell = gtk_list_item_manager_get_root (self->item_manager);
pos = 0;
g_assert (pos < cell->parent.n_items);
while (cell)
if (cell->area.width == 0 || cell->area.height == 0)
return FALSE;
x = pos % self->n_columns;
y = pos / self->n_columns;
n = (cell->parent.n_items + self->n_columns - 1) / self->n_columns;
area->x = cell->area.x + x * cell->area.width / self->n_columns;
area->width = cell->area.width / MIN (cell->parent.n_items, self->n_columns);
area->y = cell->area.y + y * cell->area.height / n;
area->height = cell->area.height / n;
return TRUE;
}
static Cell *
cell_get_cell_at (GtkGridView *self,
Cell *cell,
int x,
int y,
guint *skip)
{
CellAugment *aug;
Cell *result;
aug = gtk_list_item_manager_get_item_augment (self->item_manager, cell);
if (!gdk_rectangle_contains_point (&aug->bounds, x, y))
return NULL;
result = gtk_rb_tree_node_get_left (cell);
if (result)
result = cell_get_cell_at (self, result, x, y, skip);
if (result)
return result;
result = gtk_rb_tree_node_get_right (cell);
if (result)
result = cell_get_cell_at (self, result, x, y, skip);
if (result)
return result;
if (!gdk_rectangle_contains_point (&cell->area, x, y))
return NULL;
if (skip)
{
tmp = gtk_rb_tree_node_get_left (cell);
if (tmp)
{
CellAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, tmp);
if (y < aug->size)
{
cell = tmp;
continue;
}
y -= aug->size;
pos += aug->parent.n_items;
}
if (y < cell->size)
break;
y -= cell->size;
pos += cell->parent.n_items;
cell = gtk_rb_tree_node_get_right (cell);
}
if (cell == NULL)
{
if (position)
*position = 0;
if (offset)
*offset = 0;
if (size)
*size = 0;
return NULL;
}
/* We know have the (range of) cell(s) that contains this offset.
* Now for the hard part of computing which index this actually is.
*/
if (offset || position || size)
{
guint n_items = cell->parent.n_items;
guint no_widget_rows, skip;
/* skip remaining items at end of row */
if (pos % self->n_columns)
{
skip = self->n_columns - pos % self->n_columns;
if (n_items <= skip)
{
g_warning ("ran out of items");
if (position)
*position = 0;
if (offset)
*offset = 0;
if (size)
*size = 0;
return NULL;
}
n_items -= skip;
pos += skip;
}
/* Skip all the rows this index doesn't go into */
no_widget_rows = (n_items - 1) / self->n_columns;
skip = MIN (y / self->unknown_row_height, no_widget_rows);
y -= skip * self->unknown_row_height;
pos += self->n_columns * skip;
if (position)
*position = pos;
if (offset)
*offset = y;
if (size)
{
if (skip < no_widget_rows)
*size = self->unknown_row_height;
else
*size = cell->size - no_widget_rows * self->unknown_row_height;
}
x = (x - cell->area.x) * (cell->parent.n_items % self->n_columns) / cell->area.width;
y = (y - cell->area.y) * ((cell->parent.n_items + self->n_columns - 1) / self->n_columns) / cell->area.height;
*skip = y * self->n_columns + x;
*skip = MIN (*skip, cell->parent.n_items - 1);
}
return cell;
}
/*<private>
* gtk_grid_view_get_cell_at:
* @self: a `GtkGridView`
* @x: an offset in direction opposite @self's orientation
* @y: an offset in direction of @self's orientation
* @skip: (out caller-allocates) (optional): stores the offset
* position into the items of the returned cell
*
* Gets the Cell that occupies the position at (x, y).
*
* If no Cell is assigned to those coordinates, %NULL will be returned.
* In particular that means that for an empty grid, %NULL is returned
* for any value.
*
* Returns: (nullable): The cell at (x, y)
**/
static Cell *
gtk_grid_view_get_cell_at (GtkGridView *self,
int x,
int y,
guint *skip)
{
return cell_get_cell_at (self,
gtk_list_item_manager_get_root (self->item_manager),
x, y,
skip);
}
static gboolean
gtk_grid_view_get_allocation_along (GtkListBase *base,
guint pos,
@@ -319,37 +295,12 @@ gtk_grid_view_get_allocation_along (GtkListBase *base,
int *size)
{
GtkGridView *self = GTK_GRID_VIEW (base);
Cell *cell, *tmp;
int y;
Cell *cell;
GdkRectangle area;
guint skip;
cell = gtk_list_item_manager_get_root (self->item_manager);
y = 0;
pos -= pos % self->n_columns;
while (cell)
{
tmp = gtk_rb_tree_node_get_left (cell);
if (tmp)
{
CellAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, tmp);
if (pos < aug->parent.n_items)
{
cell = tmp;
continue;
}
pos -= aug->parent.n_items;
y += aug->size;
}
if (pos < cell->parent.n_items)
break;
y += cell->size;
pos -= cell->parent.n_items;
cell = gtk_rb_tree_node_get_right (cell);
}
if (cell == NULL)
cell = gtk_list_item_manager_get_nth (self->item_manager, pos, &skip);
if (cell == NULL || !cell_get_area (self, cell, skip, &area))
{
if (offset)
*offset = 0;
@@ -358,37 +309,10 @@ gtk_grid_view_get_allocation_along (GtkListBase *base,
return FALSE;
}
/* We know have the (range of) cell(s) that contains this offset.
* Now for the hard part of computing which index this actually is.
*/
if (offset || size)
{
guint n_items = cell->parent.n_items;
guint skip;
/* skip remaining items at end of row */
if (pos % self->n_columns)
{
skip = pos % self->n_columns;
n_items -= skip;
pos -= skip;
}
/* Skip all the rows this index doesn't go into */
skip = pos / self->n_columns;
n_items -= skip * self->n_columns;
y += skip * self->unknown_row_height;
if (offset)
*offset = y;
if (size)
{
if (n_items > self->n_columns)
*size = self->unknown_row_height;
else
*size = cell->size - skip * self->unknown_row_height;
}
}
if (offset)
*offset = cell->area.y;
if (size)
*size = cell->area.height;
return TRUE;
}
@@ -400,15 +324,24 @@ gtk_grid_view_get_allocation_across (GtkListBase *base,
int *size)
{
GtkGridView *self = GTK_GRID_VIEW (base);
guint start;
Cell *cell;
GdkRectangle area;
guint skip;
pos %= self->n_columns;
start = ceil (self->column_width * pos);
cell = gtk_list_item_manager_get_nth (self->item_manager, pos, &skip);
if (cell == NULL || !cell_get_area (self, cell, skip, &area))
{
if (offset)
*offset = 0;
if (size)
*size = 0;
return FALSE;
}
if (offset)
*offset = start;
*offset = cell->area.x;
if (size)
*size = ceil (self->column_width * (pos + 1)) - start;
*size = cell->area.width;
return TRUE;
}
@@ -421,37 +354,37 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base,
cairo_rectangle_int_t *area)
{
GtkGridView *self = GTK_GRID_VIEW (base);
int offset, size;
guint pos, n_items;
Cell *cell;
guint skip;
if (across >= self->column_width * self->n_columns)
return FALSE;
n_items = gtk_list_base_get_n_items (base);
if (!gtk_grid_view_get_cell_at_y (self,
along,
&pos,
&offset,
&size))
return FALSE;
pos += floor (across / self->column_width);
if (pos >= n_items)
cell = gtk_grid_view_get_cell_at (self,
across, along,
&skip);
if (cell != NULL)
{
/* Ugh, we're in the last row and don't have enough items
* to fill the row.
* Do it the hard way then... */
pos = n_items - 1;
*position = skip + gtk_list_item_manager_get_item_position (self->item_manager, cell);
}
else
{
/* Assign the extra space at the last row to the last item
* (in case list.n_items() is not a multiple of the column count)
*/
cell = gtk_list_item_manager_get_last (self->item_manager);
if (cell == NULL ||
along < cell->area.y || along > cell->area.y + cell->area.height ||
across < cell->area.x + cell->area.width || across >= ceil (self->column_width * self->n_columns))
return FALSE;
skip = cell->parent.n_items - 1;
*position = gtk_list_base_get_n_items (base) - 1;
}
*position = pos;
if (area)
{
area->x = ceil (self->column_width * (pos % self->n_columns));
area->width = ceil (self->column_width * (1 + pos % self->n_columns)) - area->x;
area->y = along - offset;
area->height = size;
if (!cell_get_area (self, cell, skip, area))
memset (area, 0, sizeof (GdkRectangle));
}
return TRUE;
@@ -462,26 +395,19 @@ gtk_grid_view_get_items_in_rect (GtkListBase *base,
const GdkRectangle *rect)
{
GtkGridView *self = GTK_GRID_VIEW (base);
guint first_row, last_row, first_column, last_column, n_items;
guint start_pos, end_pos;
GtkBitset *result;
result = gtk_bitset_new_empty ();
n_items = gtk_list_base_get_n_items (base);
if (n_items == 0)
if (!gtk_grid_view_get_position_from_allocation (base, rect->x, rect->y, &start_pos, NULL) ||
!gtk_grid_view_get_position_from_allocation (base, rect->x + rect->width - 1, rect->y + rect->height - 1, &end_pos, NULL))
return result;
first_column = floor (rect->x / self->column_width);
last_column = floor ((rect->x + rect->width) / self->column_width);
if (!gtk_grid_view_get_cell_at_y (self, rect->y, &first_row, NULL, NULL))
first_row = rect->y < 0 ? 0 : n_items - 1;
if (!gtk_grid_view_get_cell_at_y (self, rect->y + rect->height, &last_row, NULL, NULL))
last_row = rect->y < 0 ? 0 : n_items - 1;
gtk_bitset_add_rectangle (result,
first_row + first_column,
last_column - first_column + 1,
(last_row - first_row) / self->n_columns + 1,
start_pos,
(end_pos - start_pos) % self->n_columns + 1,
(end_pos - start_pos) / self->n_columns + 1,
self->n_columns);
return result;
@@ -712,27 +638,31 @@ gtk_grid_view_measure (GtkWidget *widget,
}
static void
cell_set_size (Cell *cell,
guint size)
cell_set_position (Cell *cell,
int x,
int y)
{
if (cell->size == size)
if (cell->area.x == x &&
cell->area.y == y)
return;
cell->size = size;
cell->area.x = x;
cell->area.y = y;
gtk_rb_tree_node_mark_dirty (cell);
}
static int
gtk_grid_view_compute_total_height (GtkGridView *self)
static void
cell_set_size (Cell *cell,
int width,
int height)
{
Cell *cell;
CellAugment *aug;
if (cell->area.width == width &&
cell->area.height == height)
return;
cell = gtk_list_item_manager_get_root (self->item_manager);
if (cell == NULL)
return 0;
aug = gtk_list_item_manager_get_item_augment (self->item_manager, cell);
return aug->size;
cell->area.width = width;
cell->area.height = height;
gtk_rb_tree_node_mark_dirty (cell);
}
static void
@@ -742,12 +672,11 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
int baseline)
{
GtkGridView *self = GTK_GRID_VIEW (widget);
Cell *cell, *start;
Cell *cell, *row_cell;
GArray *heights;
int min_row_height, row_height, col_min, col_nat;
int min_row_height, row_height, total_height, col_min, col_nat;
GtkOrientation orientation, opposite_orientation;
GtkScrollablePolicy scroll_policy;
gboolean known;
int x, y;
guint i;
@@ -756,11 +685,14 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
opposite_orientation = OPPOSITE_ORIENTATION (orientation);
min_row_height = ceil ((double) height / GTK_GRID_VIEW_MAX_VISIBLE_ROWS);
/* step 0: exit early if list is empty */
/* step 1: Clean up, so the items tracking deleted rows go away */
gtk_list_item_manager_gc (self->item_manager);
/* step 2: exit early if list is empty */
if (gtk_list_item_manager_get_root (self->item_manager) == NULL)
return;
/* step 1: determine width of the list */
/* step 3: determine width of the list */
gtk_grid_view_measure_column_size (self, &col_min, &col_nat);
self->n_columns = gtk_grid_view_compute_n_columns (self,
orientation == GTK_ORIENTATION_VERTICAL ? width : height,
@@ -768,146 +700,118 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
self->column_width = (orientation == GTK_ORIENTATION_VERTICAL ? width : height) / self->n_columns;
self->column_width = MAX (self->column_width, col_min);
/* step 2: determine height of known rows */
/* step 4: determine height of known rows */
heights = g_array_new (FALSE, FALSE, sizeof (int));
total_height = 0;
cell = gtk_list_item_manager_get_first (self->item_manager);
while (cell)
{
if (cell->parent.n_items >= MAX (2, self->n_columns))
{
int remainder = cell->parent.n_items % self->n_columns;
if (remainder > 0)
gtk_list_item_manager_split_item (self->item_manager, cell, cell->parent.n_items - remainder);
cell = gtk_rb_tree_node_get_next (cell);
continue;
}
i = 0;
row_height = 0;
for (row_cell = cell;
row_cell && i < self->n_columns;
row_cell = gtk_rb_tree_node_get_next (row_cell))
{
if (row_cell->parent.widget)
{
int min, nat, size;
gtk_widget_measure (row_cell->parent.widget,
gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
self->column_width,
&min, &nat, NULL, NULL);
if (scroll_policy == GTK_SCROLL_MINIMUM)
size = min;
else
size = nat;
size = MAX (size, min_row_height);
g_array_append_val (heights, size);
row_height = MAX (row_height, size);
}
else if (row_cell->parent.n_items > self->n_columns - i)
{
gtk_list_item_manager_split_item (self->item_manager, row_cell, self->n_columns - i);
}
i += row_cell->parent.n_items;
}
for (i = 0;
cell != row_cell;
cell = gtk_rb_tree_node_get_next (cell))
{
cell_set_size (cell, ceil (self->column_width * (i + cell->parent.n_items)) - ceil (self->column_width * i), row_height);
i += cell->parent.n_items;
}
total_height += row_height;
}
/* step 5: determine height of rows with only unknown items and assign their size */
self->unknown_row_height = gtk_grid_view_get_unknown_row_size (self, heights);
g_array_free (heights, TRUE);
i = 0;
row_height = 0;
start = NULL;
for (cell = gtk_list_item_manager_get_first (self->item_manager);
cell != NULL;
cell = gtk_rb_tree_node_get_next (cell))
{
if (i == 0)
start = cell;
if (cell->parent.widget)
{
int min, nat, size;
gtk_widget_measure (cell->parent.widget,
gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
self->column_width,
&min, &nat, NULL, NULL);
if (scroll_policy == GTK_SCROLL_MINIMUM)
size = min;
else
size = nat;
size = MAX (size, min_row_height);
g_array_append_val (heights, size);
row_height = MAX (row_height, size);
}
cell_set_size (cell, 0);
i += cell->parent.n_items;
if (cell->parent.n_items < self->n_columns)
continue;
if (i >= self->n_columns)
{
i %= self->n_columns;
cell_set_size (start, start->size + row_height);
start = cell;
row_height = 0;
}
cell_set_size (cell,
ceil (self->column_width * self->n_columns),
self->unknown_row_height * (cell->parent.n_items / self->n_columns));
total_height += self->unknown_row_height * (cell->parent.n_items / self->n_columns);
}
if (i > 0)
cell_set_size (start, start->size + row_height);
/* step 3: determine height of rows with only unknown items */
self->unknown_row_height = gtk_grid_view_get_unknown_row_size (self, heights);
g_array_free (heights, TRUE);
/* step 6: assign positions */
i = 0;
known = FALSE;
for (start = cell = gtk_list_item_manager_get_first (self->item_manager);
y = 0;
for (cell = gtk_list_item_manager_get_first (self->item_manager);
cell != NULL;
cell = gtk_rb_tree_node_get_next (cell))
{
if (i == 0)
start = cell;
if (cell->parent.widget)
known = TRUE;
cell_set_position (cell,
ceil (self->column_width * i),
y);
i += cell->parent.n_items;
if (i >= self->n_columns)
{
if (!known)
cell_set_size (start, start->size + self->unknown_row_height);
i -= self->n_columns;
known = FALSE;
if (i >= self->n_columns)
{
cell_set_size (cell, cell->size + self->unknown_row_height * (i / self->n_columns));
i %= self->n_columns;
}
start = cell;
y += cell->area.height;
i = 0;
}
}
if (i > 0 && !known)
cell_set_size (start, start->size + self->unknown_row_height);
/* step 4: update the adjustments */
/* step 7: update the adjustments */
gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
self->column_width * self->n_columns,
gtk_grid_view_compute_total_height (self),
total_height,
gtk_widget_get_size (widget, opposite_orientation),
gtk_widget_get_size (widget, orientation),
&x, &y);
/* step 5: run the size_allocate loop */
x = -x;
y = -y;
i = 0;
row_height = 0;
/* step 8: run the size_allocate loop */
for (cell = gtk_list_item_manager_get_first (self->item_manager);
cell != NULL;
cell = gtk_rb_tree_node_get_next (cell))
{
if (cell->parent.widget)
{
row_height += cell->size;
gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
cell->parent.widget,
x + ceil (self->column_width * i),
y,
ceil (self->column_width * (i + 1)) - ceil (self->column_width * i),
row_height);
i++;
if (i >= self->n_columns)
{
y += row_height;
i -= self->n_columns;
row_height = 0;
}
}
else
{
i += cell->parent.n_items;
/* skip remaining row if we didn't start one */
if (i > cell->parent.n_items && i >= self->n_columns)
{
i -= self->n_columns;
y += row_height;
row_height = 0;
}
row_height += cell->size;
/* skip rows that are completely contained by this cell */
if (i >= self->n_columns)
{
guint unknown_rows, unknown_height;
unknown_rows = i / self->n_columns;
unknown_height = unknown_rows * self->unknown_row_height;
row_height -= unknown_height;
y += unknown_height;
i %= self->n_columns;
g_assert (row_height >= 0);
}
cell->area.x - x,
cell->area.y - y,
cell->area.width,
cell->area.height);
}
}

View File

@@ -122,6 +122,11 @@ static GParamSpec *properties[N_PROPS] = { NULL, };
* last item will be returned for the whole width, even if there are empty
* cells.
*
* It is also possible for the area to be empty (ie have 0 width and height).
* This can happen when the widget has queued a resize and no current
* allocation information is available for the position or when the position
* has been newly added to the model.
*
* Returns: %TRUE on success or %FALSE if no position occupies the given offset.
**/
static guint
@@ -1524,8 +1529,8 @@ gtk_list_base_start_rubberband (GtkListBase *self,
priv->rubberband->start_tracker = gtk_list_item_tracker_new (priv->item_manager);
gtk_list_item_tracker_set_position (priv->item_manager, priv->rubberband->start_tracker, pos, 0, 0);
priv->rubberband->start_align_across = (double) (list_x - item_area.x) / item_area.width;
priv->rubberband->start_align_along = (double) (list_y - item_area.y) / item_area.height;
priv->rubberband->start_align_across = item_area.width ? (double) (list_x - item_area.x) / item_area.width : 0.5;
priv->rubberband->start_align_along = item_area.height ? (double) (list_y - item_area.y) / item_area.height : 0.5;
priv->rubberband->pointer_x = x;
priv->rubberband->pointer_y = y;

View File

@@ -145,6 +145,12 @@ gtk_list_item_manager_get_first (GtkListItemManager *self)
return gtk_rb_tree_get_first (self->items);
}
gpointer
gtk_list_item_manager_get_last (GtkListItemManager *self)
{
return gtk_rb_tree_get_last (self->items);
}
gpointer
gtk_list_item_manager_get_root (GtkListItemManager *self)
{
@@ -375,7 +381,8 @@ gtk_list_item_manager_remove_items (GtkListItemManager *self,
gtk_list_item_manager_release_list_item (self, change, item->widget);
item->widget = NULL;
n_items -= item->n_items;
gtk_rb_tree_remove (self->items, item);
item->n_items = 0;
gtk_rb_tree_node_mark_dirty (item);
item = next;
}
}
@@ -404,6 +411,25 @@ gtk_list_item_manager_add_items (GtkListItemManager *self,
gtk_widget_queue_resize (GTK_WIDGET (self->widget));
}
gpointer
gtk_list_item_manager_split_item (GtkListItemManager *self,
gpointer itemp,
guint size)
{
GtkListItemManagerItem *item = itemp;
GtkListItemManagerItem *new_item;
g_assert (size > 0 && size < item->n_items);
new_item = gtk_rb_tree_insert_after (self->items, item);
new_item->n_items = item->n_items - size;
gtk_rb_tree_node_mark_dirty (new_item);
item->n_items = size;
gtk_rb_tree_node_mark_dirty (item);
return new_item;
}
static gboolean
gtk_list_item_manager_merge_list_items (GtkListItemManager *self,
GtkListItemManagerItem *first,
@@ -419,11 +445,36 @@ gtk_list_item_manager_merge_list_items (GtkListItemManager *self,
return TRUE;
}
void
gtk_list_item_manager_gc (GtkListItemManager *self)
{
GtkListItemManagerItem *item, *next;
item = gtk_rb_tree_get_first (self->items);
while (item)
{
next = gtk_rb_tree_node_get_next (item);
if (item->n_items == 0)
{
gtk_rb_tree_remove (self->items, item);
item = next;
continue;
}
if (next && gtk_list_item_manager_merge_list_items (self, item, next))
continue;
item = next;
}
}
static void
gtk_list_item_manager_release_items (GtkListItemManager *self,
GQueue *released)
{
GtkListItemManagerItem *item, *prev, *next;
GtkListItemManagerItem *item;
guint position, i, n_items, query_n_items;
gboolean tracked;
@@ -448,28 +499,9 @@ gtk_list_item_manager_release_items (GtkListItemManager *self,
{
g_queue_push_tail (released, item->widget);
item->widget = NULL;
i++;
prev = gtk_rb_tree_node_get_previous (item);
if (prev && gtk_list_item_manager_merge_list_items (self, prev, item))
item = prev;
next = gtk_rb_tree_node_get_next (item);
if (next && next->widget == NULL)
{
i += next->n_items;
if (!gtk_list_item_manager_merge_list_items (self, next, item))
g_assert_not_reached ();
item = gtk_rb_tree_node_get_next (next);
}
else
{
item = next;
}
}
else
{
i += item->n_items;
item = gtk_rb_tree_node_get_next (item);
}
i += item->n_items;
item = gtk_rb_tree_node_get_next (item);
}
position += query_n_items;
}
@@ -522,6 +554,8 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
for (i = 0; i < query_n_items; i++)
{
g_assert (item != NULL);
while (item->n_items == 0)
item = gtk_rb_tree_node_get_next (item);
if (item->n_items > 1)
{
new_item = gtk_rb_tree_insert_before (self->items, item);

View File

@@ -73,6 +73,7 @@ void gtk_list_item_manager_augment_node (GtkRbTree
gpointer right);
gpointer gtk_list_item_manager_get_root (GtkListItemManager *self);
gpointer gtk_list_item_manager_get_first (GtkListItemManager *self);
gpointer gtk_list_item_manager_get_last (GtkListItemManager *self);
gpointer gtk_list_item_manager_get_nth (GtkListItemManager *self,
guint position,
guint *offset);
@@ -81,6 +82,11 @@ guint gtk_list_item_manager_get_item_position (GtkListItemMana
gpointer gtk_list_item_manager_get_item_augment (GtkListItemManager *self,
gpointer item);
gpointer gtk_list_item_manager_split_item (GtkListItemManager *self,
gpointer item,
guint size);
void gtk_list_item_manager_gc (GtkListItemManager *self);
void gtk_list_item_manager_set_factory (GtkListItemManager *self,
GtkListItemFactory *factory);
GtkListItemFactory * gtk_list_item_manager_get_factory (GtkListItemManager *self);

View File

@@ -213,7 +213,7 @@ list_row_augment (GtkRbTree *tree,
gtk_list_item_manager_augment_node (tree, node_augment, node, left, right);
aug->height = row->height * row->parent.n_items;
aug->height = row->height;
if (left)
{
@@ -253,9 +253,9 @@ gtk_list_view_get_row_at_y (GtkListView *self,
y -= aug->height;
}
if (y < row->height * row->parent.n_items)
if (y < row->height)
break;
y -= row->height * row->parent.n_items;
y -= row->height;
row = gtk_rb_tree_node_get_right (row);
}
@@ -295,7 +295,7 @@ list_row_get_y (GtkListView *self,
ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, left);
y += aug->height;
}
y += parent->height * parent->parent.n_items;
y += parent->height;
}
row = parent;
@@ -304,20 +304,6 @@ list_row_get_y (GtkListView *self,
return y ;
}
static int
gtk_list_view_get_list_height (GtkListView *self)
{
ListRow *row;
ListRowAugment *aug;
row = gtk_list_item_manager_get_root (self->item_manager);
if (row == NULL)
return 0;
aug = gtk_list_item_manager_get_item_augment (self->item_manager, row);
return aug->height;
}
static gboolean
gtk_list_view_get_allocation_along (GtkListBase *base,
guint pos,
@@ -340,12 +326,13 @@ gtk_list_view_get_allocation_along (GtkListBase *base,
}
y = list_row_get_y (self, row);
y += skip * row->height;
g_assert (row->parent.n_items > 0);
y += skip * row->height / row->parent.n_items;
if (offset)
*offset = y;
if (size)
*size = row->height;
*size = row->height / row->parent.n_items;
return TRUE;
}
@@ -420,7 +407,7 @@ gtk_list_view_get_position_from_allocation (GtkListBase *base,
{
GtkListView *self = GTK_LIST_VIEW (base);
ListRow *row;
int remaining;
int remaining, one_item_height;
if (across >= self->list_width)
return FALSE;
@@ -430,15 +417,24 @@ gtk_list_view_get_position_from_allocation (GtkListBase *base,
return FALSE;
*pos = gtk_list_item_manager_get_item_position (self->item_manager, row);
g_assert (remaining < row->height * row->parent.n_items);
*pos += remaining / row->height;
if (row->parent.n_items == 0)
{
row = gtk_list_item_manager_get_nth (self->item_manager, *pos, NULL);
remaining = 0;
one_item_height = 1;
}
else
one_item_height = row->height / row->parent.n_items;
g_assert (remaining < row->height);
*pos += remaining / one_item_height;
if (area)
{
area->x = 0;
area->width = self->list_width;
area->y = along - remaining % row->height;
area->height = row->height;
area->y = along - remaining % one_item_height;
area->height = one_item_height;
}
return TRUE;
@@ -584,7 +580,7 @@ gtk_list_view_size_allocate (GtkWidget *widget,
GtkListView *self = GTK_LIST_VIEW (widget);
ListRow *row;
GArray *heights;
int min, nat, row_height;
int min, nat, row_height, total_height;
int x, y;
GtkOrientation orientation, opposite_orientation;
GtkScrollablePolicy scroll_policy, opposite_scroll_policy;
@@ -594,11 +590,14 @@ gtk_list_view_size_allocate (GtkWidget *widget,
scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), orientation);
opposite_scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), opposite_orientation);
/* step 0: exit early if list is empty */
/* step 1: Clean up, so the items tracking deleted rows go away */
gtk_list_item_manager_gc (self->item_manager);
/* step 2: exit early if list is empty */
if (gtk_list_item_manager_get_root (self->item_manager) == NULL)
return;
/* step 1: determine width of the list */
/* step 3: determine width of the list */
gtk_widget_measure (widget, opposite_orientation,
-1,
&min, &nat, NULL, NULL);
@@ -608,8 +607,9 @@ gtk_list_view_size_allocate (GtkWidget *widget,
else
self->list_width = MAX (nat, self->list_width);
/* step 2: determine height of known list items */
/* step 4: determine height of known list items */
heights = g_array_new (FALSE, FALSE, sizeof (int));
total_height = 0;
for (row = gtk_list_item_manager_get_first (self->item_manager);
row != NULL;
@@ -630,10 +630,11 @@ gtk_list_view_size_allocate (GtkWidget *widget,
row->height = row_height;
gtk_rb_tree_node_mark_dirty (row);
}
total_height += row->height;
g_array_append_val (heights, row_height);
}
/* step 3: determine height of unknown items */
/* step 5: determine height of unknown items */
row_height = gtk_list_view_get_unknown_row_height (self, heights);
g_array_free (heights, TRUE);
@@ -644,25 +645,25 @@ gtk_list_view_size_allocate (GtkWidget *widget,
if (row->parent.widget)
continue;
if (row->height != row_height)
if (row->height != row->parent.n_items * row_height)
{
row->height = row_height;
row->height = row->parent.n_items * row_height;
gtk_rb_tree_node_mark_dirty (row);
}
total_height += row->height;
}
/* step 3: update the adjustments */
/* step 6: update the adjustments */
gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
self->list_width,
gtk_list_view_get_list_height (self),
total_height,
gtk_widget_get_size (widget, opposite_orientation),
gtk_widget_get_size (widget, orientation),
&x, &y);
x = -x;
y = -y;
/* step 4: actually allocate the widgets */
/* step 7: actually allocate the widgets */
for (row = gtk_list_item_manager_get_first (self->item_manager);
row != NULL;
row = gtk_rb_tree_node_get_next (row))
@@ -677,7 +678,7 @@ gtk_list_view_size_allocate (GtkWidget *widget,
row->height);
}
y += row->height * row->parent.n_items;
y += row->height;
}
gtk_list_base_allocate_rubberband (GTK_LIST_BASE (self));