Optimize file chooser icon view zoom invalidation
Only clear the visible icon-view pixbuf cache immediately while the user changes the icon size, instead of walking the whole file model for every slider step. When shrinking icons, clear a small dynamic margin around the visible range because additional rows can become visible after the layout changes. Queue one follow-up clear after GtkIconView has recalculated its layout, and coalesce repeated follow-up clears while the slider is moving. Keep a delayed full cache clear so off-screen rows become consistent after zooming settles, without repeatedly processing large folders during fast slider movement. This reduces unnecessary work in large folders without adding pixbuf caches or allocating images for unused icon sizes.
This commit is contained in:
parent
1ce1fa358a
commit
8f84c348ff
4 changed files with 247 additions and 38 deletions
|
|
@ -256,6 +256,7 @@ typedef enum {
|
|||
#define FALLBACK_ICON_VIEW_ICON_SIZE 48
|
||||
|
||||
#define ICON_VIEW_ITEM_WIDTH 128
|
||||
#define ICON_VIEW_SCALE_MAX_VISIBLE_OVERSCAN 128
|
||||
|
||||
#define PREVIEW_HBOX_SPACING 12
|
||||
#define NUM_LINES 45
|
||||
|
|
@ -415,6 +416,9 @@ static void icon_view_scale_value_changed_cb (GtkRange *range,
|
|||
GtkFileChooserDefault *impl);
|
||||
static void icon_view_scale_remove_timeout (GtkFileChooserDefault *impl);
|
||||
static void icon_view_scale_queue_cache_clear (GtkFileChooserDefault *impl);
|
||||
static gboolean get_visible_range (GtkTreePath **start,
|
||||
GtkTreePath **end,
|
||||
GtkFileChooserDefault *impl);
|
||||
|
||||
static void location_button_toggled_cb (GtkToggleButton *toggle,
|
||||
GtkFileChooserDefault *impl);
|
||||
|
|
@ -5277,7 +5281,14 @@ icon_view_scale_remove_visible_timeout (GtkFileChooserDefault *impl)
|
|||
impl->icon_view_scale_visible_timeout_id = 0;
|
||||
}
|
||||
|
||||
if (impl->icon_view_scale_after_layout_id)
|
||||
{
|
||||
g_source_remove (impl->icon_view_scale_after_layout_id);
|
||||
impl->icon_view_scale_after_layout_id = 0;
|
||||
}
|
||||
|
||||
impl->pending_icon_view_icon_size = 0;
|
||||
impl->icon_view_scale_visible_overscan = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -5295,6 +5306,127 @@ icon_view_scale_clear_model_cache (GtkFileChooserDefault *impl,
|
|||
_gtk_file_system_model_clear_cache (model, MODEL_COL_ICON_PIXBUF);
|
||||
}
|
||||
|
||||
static void icon_view_scale_clear_visible_model_cache (GtkFileChooserDefault *impl,
|
||||
gboolean include_overscan);
|
||||
|
||||
static gboolean
|
||||
icon_view_scale_clear_visible_after_layout_cb (gpointer data)
|
||||
{
|
||||
GtkFileChooserDefault *impl = data;
|
||||
|
||||
impl->icon_view_scale_after_layout_id = 0;
|
||||
|
||||
if (impl->view_mode == VIEW_MODE_ICON && impl->browse_files_icon_view)
|
||||
{
|
||||
icon_view_scale_clear_visible_model_cache (impl, TRUE);
|
||||
gtk_widget_queue_draw (impl->browse_files_icon_view);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
icon_view_scale_queue_visible_after_layout_clear (GtkFileChooserDefault *impl)
|
||||
{
|
||||
/*
|
||||
* GtkIconView recalculates item positions in an idle handler. This follow-up
|
||||
* pass is only queued when shrinking icons, because only shrinking can make
|
||||
* additional rows visible.
|
||||
*
|
||||
* Coalesce repeated shrink updates into one idle pass; otherwise fast slider
|
||||
* motion can queue many redundant visible-range + overscan clears.
|
||||
*/
|
||||
if (impl->icon_view_scale_after_layout_id)
|
||||
return;
|
||||
|
||||
impl->icon_view_scale_after_layout_id =
|
||||
gdk_threads_add_idle_full (G_PRIORITY_DEFAULT_IDLE,
|
||||
icon_view_scale_clear_visible_after_layout_cb,
|
||||
g_object_ref (impl),
|
||||
(GDestroyNotify) g_object_unref);
|
||||
}
|
||||
|
||||
static void
|
||||
icon_view_scale_clear_visible_model_cache (GtkFileChooserDefault *impl,
|
||||
gboolean include_overscan)
|
||||
{
|
||||
GtkTreeModel *tree_model;
|
||||
GtkTreePath *start, *end, *path;
|
||||
GtkTreeIter iter;
|
||||
gint *start_indices;
|
||||
gint *end_indices;
|
||||
gint first;
|
||||
gint last;
|
||||
gint i;
|
||||
|
||||
if (!impl->current_model || !GTK_IS_FILE_SYSTEM_MODEL (impl->current_model))
|
||||
return;
|
||||
|
||||
/*
|
||||
* A full _gtk_file_system_model_clear_cache() is O(total files). During
|
||||
* icon-view zooming we refresh only the visible range.
|
||||
*
|
||||
* When shrinking icons, the new layout can make rows visible that were just
|
||||
* outside the old visible range. Only in that case do we add a small margin.
|
||||
*/
|
||||
if (!get_visible_range (&start, &end, impl))
|
||||
{
|
||||
_gtk_file_system_model_clear_cache (GTK_FILE_SYSTEM_MODEL (impl->current_model),
|
||||
MODEL_COL_ICON_PIXBUF);
|
||||
return;
|
||||
}
|
||||
|
||||
start_indices = gtk_tree_path_get_indices (start);
|
||||
end_indices = gtk_tree_path_get_indices (end);
|
||||
|
||||
if (!start_indices || !end_indices)
|
||||
{
|
||||
gtk_tree_path_free (start);
|
||||
gtk_tree_path_free (end);
|
||||
_gtk_file_system_model_clear_cache (GTK_FILE_SYSTEM_MODEL (impl->current_model),
|
||||
MODEL_COL_ICON_PIXBUF);
|
||||
return;
|
||||
}
|
||||
|
||||
if (include_overscan)
|
||||
{
|
||||
gint overscan;
|
||||
|
||||
overscan = impl->icon_view_scale_visible_overscan;
|
||||
if (overscan <= 0)
|
||||
overscan = 16;
|
||||
|
||||
overscan = MIN (overscan, ICON_VIEW_SCALE_MAX_VISIBLE_OVERSCAN);
|
||||
|
||||
first = MAX (0, start_indices[0] - overscan);
|
||||
last = end_indices[0] + overscan;
|
||||
}
|
||||
else
|
||||
{
|
||||
first = start_indices[0];
|
||||
last = end_indices[0];
|
||||
}
|
||||
|
||||
tree_model = impl->current_model;
|
||||
path = gtk_tree_path_new_from_indices (first, -1);
|
||||
|
||||
for (i = first; i <= last; i++)
|
||||
{
|
||||
if (!gtk_tree_model_get_iter (tree_model, &iter, path))
|
||||
break;
|
||||
|
||||
_gtk_file_system_model_clear_cache_for_iter (GTK_FILE_SYSTEM_MODEL (tree_model),
|
||||
&iter,
|
||||
MODEL_COL_ICON_PIXBUF);
|
||||
|
||||
gtk_tree_path_next (path);
|
||||
}
|
||||
|
||||
gtk_tree_path_free (path);
|
||||
gtk_tree_path_free (start);
|
||||
gtk_tree_path_free (end);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
icon_view_scale_clear_cache_cb (gpointer data)
|
||||
{
|
||||
|
|
@ -5303,18 +5435,21 @@ icon_view_scale_clear_cache_cb (gpointer data)
|
|||
impl->icon_view_scale_timeout_id = 0;
|
||||
|
||||
/*
|
||||
* The currently visible model is refreshed through a much shorter
|
||||
* timeout. This longer timeout only catches inactive models so that
|
||||
* switching to Search/Recent/Browse later does not reuse pixbufs from
|
||||
* an older icon-view scale.
|
||||
* Visible rows are refreshed quickly while the user drags the zoom slider.
|
||||
* Once zooming has settled, clear all file-system model icon caches once so
|
||||
* off-screen rows do not keep pixbufs generated for an older icon size.
|
||||
*
|
||||
* This is intentionally delayed: large folders should not be walked for every
|
||||
* intermediate slider value, but the cache must become globally consistent
|
||||
* after the user stops zooming.
|
||||
*/
|
||||
if (impl->current_model != GTK_TREE_MODEL (impl->browse_files_model))
|
||||
icon_view_scale_clear_model_cache (impl, impl->browse_files_model);
|
||||
if (impl->current_model != GTK_TREE_MODEL (impl->search_model))
|
||||
icon_view_scale_clear_model_cache (impl, impl->search_model);
|
||||
if (impl->current_model != GTK_TREE_MODEL (impl->recent_model))
|
||||
icon_view_scale_clear_model_cache (impl, impl->recent_model);
|
||||
|
||||
if (impl->view_mode == VIEW_MODE_ICON && impl->browse_files_icon_view)
|
||||
gtk_widget_queue_resize (impl->browse_files_icon_view);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
|
@ -5322,7 +5457,9 @@ static gboolean
|
|||
icon_view_scale_apply_visible_size_cb (gpointer data)
|
||||
{
|
||||
GtkFileChooserDefault *impl = data;
|
||||
gint old_icon_size;
|
||||
gint icon_size;
|
||||
gboolean shrinking;
|
||||
|
||||
impl->icon_view_scale_visible_timeout_id = 0;
|
||||
|
||||
|
|
@ -5332,9 +5469,32 @@ icon_view_scale_apply_visible_size_cb (gpointer data)
|
|||
if (icon_size <= 0)
|
||||
icon_size = FALLBACK_ICON_VIEW_ICON_SIZE;
|
||||
|
||||
if (impl->icon_view_icon_size == icon_size)
|
||||
old_icon_size = impl->icon_view_icon_size;
|
||||
|
||||
if (old_icon_size == icon_size)
|
||||
return FALSE;
|
||||
|
||||
shrinking = icon_size < old_icon_size;
|
||||
|
||||
if (shrinking)
|
||||
{
|
||||
gint delta;
|
||||
|
||||
delta = old_icon_size - icon_size;
|
||||
|
||||
/*
|
||||
* A small shrink only exposes a few additional rows; a large shrink can
|
||||
* expose many. Scale the overscan with the size delta so normal slider
|
||||
* movement does not always pay the maximum 128-row cost.
|
||||
*/
|
||||
impl->icon_view_scale_visible_overscan =
|
||||
CLAMP (delta * 2, 16, ICON_VIEW_SCALE_MAX_VISIBLE_OVERSCAN);
|
||||
}
|
||||
else
|
||||
{
|
||||
impl->icon_view_scale_visible_overscan = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply the visible zoom as one atomic UI update: size, cell layout,
|
||||
* visible pixbuf cache and resize are kept in sync. Delaying only the
|
||||
|
|
@ -5348,16 +5508,13 @@ icon_view_scale_apply_visible_size_cb (gpointer data)
|
|||
return FALSE;
|
||||
|
||||
update_icon_view_cell_layout (impl);
|
||||
|
||||
if (impl->current_model == GTK_TREE_MODEL (impl->browse_files_model))
|
||||
icon_view_scale_clear_model_cache (impl, impl->browse_files_model);
|
||||
else if (impl->current_model == GTK_TREE_MODEL (impl->search_model))
|
||||
icon_view_scale_clear_model_cache (impl, impl->search_model);
|
||||
else if (impl->current_model == GTK_TREE_MODEL (impl->recent_model))
|
||||
icon_view_scale_clear_model_cache (impl, impl->recent_model);
|
||||
icon_view_scale_clear_visible_model_cache (impl, shrinking);
|
||||
|
||||
gtk_widget_queue_resize (impl->browse_files_icon_view);
|
||||
|
||||
if (shrinking && impl->icon_view_scale_visible_overscan > 0)
|
||||
icon_view_scale_queue_visible_after_layout_clear (impl);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
|
@ -5394,7 +5551,7 @@ icon_view_scale_queue_cache_clear (GtkFileChooserDefault *impl)
|
|||
* are applied by the short 16ms timeout.
|
||||
*/
|
||||
impl->icon_view_scale_timeout_id =
|
||||
gdk_threads_add_timeout (500, icon_view_scale_clear_cache_cb, impl);
|
||||
gdk_threads_add_timeout (1000, icon_view_scale_clear_cache_cb, impl);
|
||||
}
|
||||
|
||||
/* Callback used when the icon view scale is changed */
|
||||
|
|
@ -6776,15 +6933,23 @@ settings_load (GtkFileChooserDefault *impl)
|
|||
|
||||
g_object_unref (settings);
|
||||
|
||||
location_mode_set (impl, location_mode, TRUE);
|
||||
view_mode_set (impl, view_mode);
|
||||
|
||||
if (icon_view_scale <= 0)
|
||||
icon_view_scale = FALLBACK_ICON_VIEW_ICON_SIZE;
|
||||
|
||||
gtk_range_set_value (GTK_RANGE (impl->icon_view_scale), icon_view_scale);
|
||||
impl->icon_view_icon_size = icon_view_scale;
|
||||
|
||||
location_mode_set (impl, location_mode, TRUE);
|
||||
|
||||
g_signal_handlers_block_by_func (impl->icon_view_scale,
|
||||
G_CALLBACK (icon_view_scale_value_changed_cb),
|
||||
impl);
|
||||
gtk_range_set_value (GTK_RANGE (impl->icon_view_scale), icon_view_scale);
|
||||
g_signal_handlers_unblock_by_func (impl->icon_view_scale,
|
||||
G_CALLBACK (icon_view_scale_value_changed_cb),
|
||||
impl);
|
||||
|
||||
view_mode_set (impl, view_mode);
|
||||
|
||||
gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (impl), show_hidden);
|
||||
|
||||
impl->show_size_column = show_size_column;
|
||||
|
|
@ -11524,16 +11689,15 @@ current_view_set_file_model (GtkFileChooserDefault *impl, GtkTreeModel *model)
|
|||
g_object_set (view, "model", impl->current_model, NULL);
|
||||
|
||||
/*
|
||||
* If an icon-view scale change has queued a delayed clear for inactive
|
||||
* models, a model can become visible before that timeout fires. Clear the
|
||||
* newly visible model immediately so it cannot show pixbufs cached for an
|
||||
* older icon size.
|
||||
* If an icon-view scale change has queued a delayed full cache clear, a model
|
||||
* can become visible before that timeout fires. Clear the newly visible model
|
||||
* immediately so it cannot show pixbufs cached for an older icon size.
|
||||
*/
|
||||
if (impl->view_mode == VIEW_MODE_ICON &&
|
||||
impl->icon_view_scale_timeout_id &&
|
||||
GTK_IS_FILE_SYSTEM_MODEL (impl->current_model))
|
||||
icon_view_scale_clear_model_cache (impl,
|
||||
GTK_FILE_SYSTEM_MODEL (impl->current_model));
|
||||
_gtk_file_system_model_clear_cache (GTK_FILE_SYSTEM_MODEL (impl->current_model),
|
||||
MODEL_COL_ICON_PIXBUF);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
|||
|
|
@ -234,7 +234,9 @@ struct _GtkFileChooserDefault
|
|||
GtkWidget *icon_view_scale_zoom_out_icon;
|
||||
guint icon_view_scale_timeout_id;
|
||||
guint icon_view_scale_visible_timeout_id;
|
||||
guint icon_view_scale_after_layout_id;
|
||||
gint pending_icon_view_icon_size;
|
||||
gint icon_view_scale_visible_overscan;
|
||||
ViewMode view_mode;
|
||||
|
||||
GtkWidget *location_button;
|
||||
|
|
|
|||
|
|
@ -2102,6 +2102,7 @@ _gtk_file_system_model_clear_cache (GtkFileSystemModel *model,
|
|||
for (i = 0; i < model->files->len; i++)
|
||||
{
|
||||
FileModelNode *node = get_node (model, i);
|
||||
|
||||
changed = FALSE;
|
||||
for (column = start; column < end; column++)
|
||||
{
|
||||
|
|
@ -2119,6 +2120,46 @@ _gtk_file_system_model_clear_cache (GtkFileSystemModel *model,
|
|||
/* FIXME: resort? */
|
||||
}
|
||||
|
||||
void
|
||||
_gtk_file_system_model_clear_cache_for_iter (GtkFileSystemModel *model,
|
||||
GtkTreeIter *iter,
|
||||
int column)
|
||||
{
|
||||
FileModelNode *node;
|
||||
int start, end;
|
||||
gboolean changed = FALSE;
|
||||
|
||||
g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model));
|
||||
g_return_if_fail (iter != NULL);
|
||||
g_return_if_fail (ITER_IS_VALID (model, iter));
|
||||
g_return_if_fail (column >= -1 && (guint) column < model->n_columns);
|
||||
|
||||
if (column > -1)
|
||||
{
|
||||
start = column;
|
||||
end = column + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = 0;
|
||||
end = model->n_columns;
|
||||
}
|
||||
|
||||
node = get_node (model, ITER_INDEX (iter));
|
||||
|
||||
for (column = start; column < end; column++)
|
||||
{
|
||||
if (!G_VALUE_TYPE (&node->values[column]))
|
||||
continue;
|
||||
|
||||
g_value_unset (&node->values[column]);
|
||||
changed = TRUE;
|
||||
}
|
||||
|
||||
if (changed && node->visible)
|
||||
emit_row_changed_for_node (model, ITER_INDEX (iter));
|
||||
}
|
||||
|
||||
/**
|
||||
* _gtk_file_system_model_add_and_query_file:
|
||||
* @model: a #GtkFileSystemModel
|
||||
|
|
|
|||
|
|
@ -85,7 +85,9 @@ void _gtk_file_system_model_set_filter_folders (GtkFileSystemMode
|
|||
gboolean show_folders);
|
||||
void _gtk_file_system_model_clear_cache (GtkFileSystemModel *model,
|
||||
int column);
|
||||
|
||||
void _gtk_file_system_model_clear_cache_for_iter (GtkFileSystemModel *model,
|
||||
GtkTreeIter *iter,
|
||||
int column);
|
||||
void _gtk_file_system_model_set_filter (GtkFileSystemModel *model,
|
||||
GtkFileFilter *filter);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue