gtkiconview: slice layout work across main loop iterations

Make GtkIconView layout resumable and time-slice row processing so large models do not monopolize the main loop.

Preserve layout state across idle iterations, avoid redundant relayout from size allocation, and keep hit testing and visible-range queries safe while layout is still in progress.
This commit is contained in:
Daemonratte 2026-09-01 11:52:17 +02:00
commit 7af69af9c8

View file

@ -120,6 +120,12 @@ struct _GtkIconViewPrivate
GtkAdjustment *vadjustment;
guint layout_idle_id;
GList *layout_items;
gint layout_y, layout_maximum_width;
gint layout_row, layout_item_width, layout_next_index;
guint layout_in_progress : 1;
guint layout_scanning_width : 1;
guint layout_restart : 1;
gboolean doing_rubberband;
gint rubberband_x1, rubberband_y1;
@ -304,6 +310,7 @@ static void gtk_icon_view_queue_draw_path (GtkIco
GtkTreePath *path);
static void gtk_icon_view_queue_draw_item (GtkIconView *icon_view,
GtkIconViewItem *item);
static gboolean layout_callback (gpointer user_data);
static void gtk_icon_view_queue_layout (GtkIconView *icon_view);
static void gtk_icon_view_set_cursor_item (GtkIconView *icon_view,
GtkIconViewItem *item,
@ -1491,6 +1498,8 @@ gtk_icon_view_size_allocate (GtkWidget *widget,
GtkIconView *icon_view = GTK_ICON_VIEW (widget);
GtkAdjustment *hadjustment, *vadjustment;
gboolean relayout = (icon_view->priv->layout_idle_id != 0 ||
widget->allocation.width != allocation->width);
widget->allocation = *allocation;
@ -1504,7 +1513,8 @@ gtk_icon_view_size_allocate (GtkWidget *widget,
MAX (icon_view->priv->height, allocation->height));
}
gtk_icon_view_layout (icon_view);
if (relayout)
gtk_icon_view_layout (icon_view);
gtk_icon_view_allocate_children (icon_view);
@ -1588,6 +1598,10 @@ gtk_icon_view_expose (GtkWidget *widget,
for (icons = icon_view->priv->items; icons; icons = icons->next)
{
GtkIconViewItem *item = icons->data;
if (icon_view->priv->layout_in_progress &&
item->index >= icon_view->priv->layout_next_index)
break;
GdkRectangle area;
area.x = item->x;
@ -2810,11 +2824,8 @@ gtk_icon_view_set_adjustment_upper (GtkAdjustment *adj,
static void
gtk_icon_view_layout (GtkIconView *icon_view)
{
gint y = 0, maximum_width = 0;
GList *icons;
GtkWidget *widget;
gint row;
gint item_width;
gint64 deadline;
if (icon_view->priv->layout_idle_id != 0)
{
@ -2823,54 +2834,100 @@ gtk_icon_view_layout (GtkIconView *icon_view)
}
if (icon_view->priv->model == NULL)
return;
{
icon_view->priv->layout_in_progress = FALSE;
icon_view->priv->layout_items = NULL;
return;
}
widget = GTK_WIDGET (icon_view);
deadline = g_get_monotonic_time () + 2000;
item_width = icon_view->priv->item_width;
if (item_width < 0)
if (!icon_view->priv->layout_in_progress ||
icon_view->priv->layout_restart)
{
for (icons = icon_view->priv->items; icons; icons = icons->next)
{
GtkIconViewItem *item = icons->data;
gtk_icon_view_calculate_item_size (icon_view, item);
item_width = MAX (item_width, item->width);
}
icon_view->priv->layout_in_progress = TRUE;
icon_view->priv->layout_restart = FALSE;
icon_view->priv->layout_items = icon_view->priv->items;
icon_view->priv->layout_y = icon_view->priv->margin;
icon_view->priv->layout_maximum_width = 0;
icon_view->priv->layout_row = 0;
icon_view->priv->layout_item_width = icon_view->priv->item_width;
icon_view->priv->layout_next_index = 0;
icon_view->priv->layout_scanning_width =
icon_view->priv->layout_item_width < 0;
if (!icon_view->priv->layout_scanning_width &&
icon_view->priv->layout_items)
{
gtk_icon_view_set_cell_data (icon_view,
icon_view->priv->layout_items->data);
adjust_wrap_width (icon_view, icon_view->priv->layout_items->data);
}
}
icons = icon_view->priv->items;
y += icon_view->priv->margin;
row = 0;
if (icons)
while (icon_view->priv->layout_scanning_width &&
icon_view->priv->layout_items)
{
gtk_icon_view_set_cell_data (icon_view, icons->data);
adjust_wrap_width (icon_view, icons->data);
GtkIconViewItem *item = icon_view->priv->layout_items->data;
gtk_icon_view_calculate_item_size (icon_view, item);
icon_view->priv->layout_item_width =
MAX (icon_view->priv->layout_item_width, item->width);
icon_view->priv->layout_items = icon_view->priv->layout_items->next;
if (g_get_monotonic_time () >= deadline)
goto yield;
}
do
if (icon_view->priv->layout_scanning_width)
{
icons = gtk_icon_view_layout_single_row (icon_view, icons,
item_width, row,
&y, &maximum_width);
row++;
icon_view->priv->layout_scanning_width = FALSE;
icon_view->priv->layout_items = icon_view->priv->items;
if (icon_view->priv->layout_items)
{
gtk_icon_view_set_cell_data (icon_view,
icon_view->priv->layout_items->data);
adjust_wrap_width (icon_view, icon_view->priv->layout_items->data);
}
}
while (icons != NULL);
if (maximum_width != icon_view->priv->width)
icon_view->priv->width = maximum_width;
while (icon_view->priv->layout_items)
{
icon_view->priv->layout_items =
gtk_icon_view_layout_single_row (icon_view,
icon_view->priv->layout_items,
icon_view->priv->layout_item_width,
icon_view->priv->layout_row++,
&icon_view->priv->layout_y,
&icon_view->priv->layout_maximum_width);
y += icon_view->priv->margin;
icon_view->priv->layout_next_index =
icon_view->priv->layout_items
? ((GtkIconViewItem *) icon_view->priv->layout_items->data)->index
: G_MAXINT;
if (y != icon_view->priv->height)
icon_view->priv->height = y;
if (icon_view->priv->layout_restart ||
(icon_view->priv->layout_items &&
g_get_monotonic_time () >= deadline))
goto yield;
}
icon_view->priv->layout_in_progress = FALSE;
if (icon_view->priv->layout_maximum_width != icon_view->priv->width)
icon_view->priv->width = icon_view->priv->layout_maximum_width;
icon_view->priv->layout_y += icon_view->priv->margin;
if (icon_view->priv->layout_y != icon_view->priv->height)
icon_view->priv->height = icon_view->priv->layout_y;
gtk_icon_view_set_adjustment_upper (icon_view->priv->hadjustment,
icon_view->priv->width);
icon_view->priv->width);
gtk_icon_view_set_adjustment_upper (icon_view->priv->vadjustment,
icon_view->priv->height);
icon_view->priv->height);
if (icon_view->priv->width != widget->requisition.width ||
icon_view->priv->height != widget->requisition.height)
@ -2878,8 +2935,8 @@ gtk_icon_view_layout (GtkIconView *icon_view)
if (gtk_widget_get_realized (GTK_WIDGET (icon_view)))
gdk_window_resize (icon_view->priv->bin_window,
MAX (icon_view->priv->width, widget->allocation.width),
MAX (icon_view->priv->height, widget->allocation.height));
MAX (icon_view->priv->width, widget->allocation.width),
MAX (icon_view->priv->height, widget->allocation.height));
if (icon_view->priv->scroll_to_path)
{
@ -2890,13 +2947,21 @@ gtk_icon_view_layout (GtkIconView *icon_view)
icon_view->priv->scroll_to_path = NULL;
gtk_icon_view_scroll_to_path (icon_view, path,
icon_view->priv->scroll_to_use_align,
icon_view->priv->scroll_to_row_align,
icon_view->priv->scroll_to_col_align);
icon_view->priv->scroll_to_use_align,
icon_view->priv->scroll_to_row_align,
icon_view->priv->scroll_to_col_align);
gtk_tree_path_free (path);
}
gtk_widget_queue_draw (widget);
return;
yield:
if (icon_view->priv->layout_idle_id == 0)
icon_view->priv->layout_idle_id =
gdk_threads_add_idle (layout_callback, icon_view);
gtk_widget_queue_draw (widget);
}
static void
@ -3409,6 +3474,9 @@ layout_callback (gpointer user_data)
static void
gtk_icon_view_queue_layout (GtkIconView *icon_view)
{
if (icon_view->priv->layout_in_progress)
icon_view->priv->layout_restart = TRUE;
if (icon_view->priv->layout_idle_id != 0)
return;
@ -3500,6 +3568,10 @@ gtk_icon_view_get_item_at_coords (GtkIconView *icon_view,
{
GtkIconViewItem *item = items->data;
if (icon_view->priv->layout_in_progress &&
item->index >= icon_view->priv->layout_next_index)
break;
if (x >= item->x - icon_view->priv->column_spacing/2 && x <= item->x + item->width + icon_view->priv->column_spacing/2 &&
y >= item->y - icon_view->priv->row_spacing/2 && y <= item->y + item->height + icon_view->priv->row_spacing/2)
{
@ -5207,6 +5279,10 @@ gtk_icon_view_get_visible_range (GtkIconView *icon_view,
{
GtkIconViewItem *item = icons->data;
if (icon_view->priv->layout_in_progress &&
item->index >= icon_view->priv->layout_next_index)
break;
if ((item->x + item->width >= (int)icon_view->priv->hadjustment->value) &&
(item->y + item->height >= (int)icon_view->priv->vadjustment->value) &&
(item->x <= (int) (icon_view->priv->hadjustment->value + icon_view->priv->hadjustment->page_size)) &&