[gtk/wip/otte/listview: 37/155] listview: Change anchor handling again



commit a4fa8245ad0dcedde98520bc592d7fca09a160f7
Author: Benjamin Otte <otte redhat com>
Date:   Tue Sep 25 00:16:27 2018 +0200

    listview: Change anchor handling again
    
    The anchor is now a tuple of { listitem, align }.
    
    Using the actual list item allows keeping the anchor across changes
    in position (ie when lists get resorted) while still being able to fall
    back to positions (list items store their position) when an item gets
    removed.
    
    The align value is in the range [0..1] and defines where in the visible
    area to do the alignment.
    0.0 means to align the top of the row with the top of the visible area,
    1.0 aligns the bottom of the widget with the visible area and 0.5 keeps
    the center of the widget at the center of the visible area.
    It works conceptually the same as percentages in CSS background-position
    (where the background area and the background image's size are matched
    the same way) or CSS transform-origin.

 gtk/gtklistitemmanager.c        |  24 +++++++
 gtk/gtklistitemmanagerprivate.h |   3 +
 gtk/gtklistview.c               | 154 +++++++++++++++++++++++++---------------
 3 files changed, 122 insertions(+), 59 deletions(-)
---
diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c
index d365137d181..cce8eda34d9 100644
--- a/gtk/gtklistitemmanager.c
+++ b/gtk/gtklistitemmanager.c
@@ -204,6 +204,30 @@ gtk_list_item_manager_end_change (GtkListItemManager       *self,
   g_slice_free (GtkListItemManagerChange, change);
 }
 
+/*
+ * gtk_list_item_manager_change_contains:
+ * @change: a #GtkListItemManagerChange
+ * @list_item: The item that may have been released into this change set
+ *
+ * Checks if @list_item has been released as part of @change but not been
+ * reacquired yet.
+ *
+ * This is useful to test before calling gtk_list_item_manager_end_change()
+ * if special actions need to be performed when important list items - like
+ * the focused item - are about to be deleted.
+ *
+ * Returns: %TRUE if the item is part of this change
+ **/
+gboolean
+gtk_list_item_manager_change_contains (GtkListItemManagerChange *change,
+                                       GtkWidget                *list_item)
+{
+  g_return_val_if_fail (change != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_LIST_ITEM (list_item), FALSE);
+
+  return g_hash_table_lookup (change->items, gtk_list_item_get_item (GTK_LIST_ITEM (list_item))) == 
list_item;
+}
+
 /*
  * gtk_list_item_manager_acquire_list_item:
  * @self: a #GtkListItemManager
diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h
index d1cdfaf8194..cf7721bc6ce 100644
--- a/gtk/gtklistitemmanagerprivate.h
+++ b/gtk/gtklistitemmanagerprivate.h
@@ -55,6 +55,9 @@ GtkListItemManagerChange *
                         gtk_list_item_manager_begin_change      (GtkListItemManager     *self);
 void                    gtk_list_item_manager_end_change        (GtkListItemManager     *self,
                                                                  GtkListItemManagerChange *change);
+gboolean                gtk_list_item_manager_change_contains   (GtkListItemManagerChange *change,
+                                                                 GtkWidget              *list_item);
+
 GtkWidget *             gtk_list_item_manager_acquire_list_item (GtkListItemManager     *self,
                                                                  guint                   position,
                                                                  GtkWidget              *next_sibling);
diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
index 36e710ab18d..35866d47b7e 100644
--- a/gtk/gtklistview.c
+++ b/gtk/gtklistview.c
@@ -54,10 +54,8 @@ struct _GtkListView
   int list_width;
 
   /* managing the visible region */
-  guint anchor_pos;
-  int anchor_dy;
-  guint first_visible_pos;
-  guint lasst_visible_pos;
+  GtkWidget *anchor;
+  int anchor_align;
 };
 
 struct _ListRow
@@ -284,6 +282,54 @@ gtk_list_view_get_list_height (GtkListView *self)
   return aug->height;
 }
 
+static void
+gtk_list_view_set_anchor (GtkListView *self,
+                          guint        position,
+                          double       align)
+{
+  ListRow *row;
+
+  g_assert (align >= 0.0 && align <= 1.0);
+
+  row = gtk_list_view_get_row (self, position, NULL);
+  if (row == NULL)
+    {
+      /* like, if the list is empty */
+      self->anchor = NULL;
+      self->anchor_align = 0.0;
+      return;
+    }
+
+  self->anchor = row->widget;
+  self->anchor_align = align;
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+static void
+gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment,
+                                           GtkListView   *self)
+{
+  if (adjustment == self->adjustment[GTK_ORIENTATION_VERTICAL])
+    {
+      ListRow *row;
+      guint pos;
+      int dy;
+
+      row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy);
+      if (row)
+        pos = list_row_get_position (self, row);
+      else
+        pos = 0;
+
+      gtk_list_view_set_anchor (self, pos, 0);
+    }
+  else
+    { 
+      gtk_widget_queue_allocate (GTK_WIDGET (self));
+    }
+}
+
 static void
 gtk_list_view_update_adjustments (GtkListView    *self,
                                   GtkOrientation  orientation)
@@ -306,15 +352,21 @@ gtk_list_view_update_adjustments (GtkListView    *self,
       page_size = gtk_widget_get_height (GTK_WIDGET (self));
       upper = gtk_list_view_get_list_height (self);
 
-      row = gtk_list_view_get_row (self, self->anchor_pos, NULL);
+      if (self->anchor)
+        row = gtk_list_view_get_row (self, gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)), NULL);
+      else
+        row = NULL;
       if (row)
         value = list_row_get_y (self, row);
       else
         value = 0;
-      value += self->anchor_dy;
+      value -= self->anchor_align * (page_size - (row ? row->height : 0));
     }
   upper = MAX (upper, page_size);
 
+  g_signal_handlers_block_by_func (self->adjustment[orientation],
+                                   gtk_list_view_adjustment_value_changed_cb,
+                                   self);
   gtk_adjustment_configure (self->adjustment[orientation],
                             value,
                             0,
@@ -322,6 +374,9 @@ gtk_list_view_update_adjustments (GtkListView    *self,
                             page_size * 0.1,
                             page_size * 0.9,
                             page_size);
+  g_signal_handlers_unblock_by_func (self->adjustment[orientation],
+                                     gtk_list_view_adjustment_value_changed_cb,
+                                     self);
 }
 
 static void
@@ -413,7 +468,7 @@ gtk_list_view_size_allocate (GtkWidget *widget,
 
   /* step 4: actually allocate the widgets */
   child_allocation.x = - gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_HORIZONTAL]);
-  child_allocation.y = - gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_VERTICAL]);
+  child_allocation.y = - round (gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_VERTICAL]));
   child_allocation.width = self->list_width;
   for (row = gtk_rb_tree_get_first (self->rows);
        row != NULL;
@@ -425,6 +480,23 @@ gtk_list_view_size_allocate (GtkWidget *widget,
     }
 }
 
+static guint
+gtk_list_view_get_anchor (GtkListView *self,
+                          double      *align)
+{
+  guint anchor_pos;
+
+  if (self->anchor)
+    anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor));
+  else
+    anchor_pos = 0;
+
+  if (align)
+    *align = self->anchor_align;
+
+  return anchor_pos;
+}
+
 static void
 gtk_list_view_remove_rows (GtkListView              *self,
                            GtkListItemManagerChange *change,
@@ -432,24 +504,11 @@ gtk_list_view_remove_rows (GtkListView              *self,
                            guint                     n_rows)
 {
   ListRow *row;
-  guint i, n_remaining;
+  guint i;
 
   if (n_rows == 0)
     return;
 
-  n_remaining = self->model ? g_list_model_get_n_items (self->model) : 0;
-  if (self->anchor_pos >= position + n_rows)
-    {
-      self->anchor_pos -= n_rows;
-    }
-  else if (self->anchor_pos >= position)
-    {
-      self->anchor_pos = position;
-      if (self->anchor_pos > 0 && self->anchor_pos >= n_remaining)
-        self->anchor_pos = n_remaining - 1;
-      self->anchor_dy = 0;
-    }
-
   row = gtk_list_view_get_row (self, position, NULL);
 
   for (i = 0; i < n_rows; i++)
@@ -471,18 +530,11 @@ gtk_list_view_add_rows (GtkListView              *self,
                         guint                     n_rows)
 {  
   ListRow *row;
-  guint i, n_total;
+  guint i;
 
   if (n_rows == 0)
     return;
 
-  n_total = self->model ? g_list_model_get_n_items (self->model) : 0;
-  if (self->anchor_pos >= position)
-    {
-      if (n_total != n_rows) /* the model was not empty before */
-        self->anchor_pos += n_rows;
-    }
-
   row = gtk_list_view_get_row (self, position, NULL);
 
   for (i = 0; i < n_rows; i++)
@@ -541,6 +593,15 @@ gtk_list_view_model_items_changed_cb (GListModel  *model,
   if (removed != added)
     gtk_list_view_update_rows (self, position + added);
 
+  if (gtk_list_item_manager_change_contains (change, self->anchor))
+    {
+      guint anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor));
+      
+      /* removed cannot be NULL or the anchor wouldn't have been removed */
+      anchor_pos = position + (anchor_pos - position) * added / removed;
+      gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align);
+    }
+
   gtk_list_item_manager_end_change (self->item_manager, change);
 }
 
@@ -558,35 +619,6 @@ gtk_list_view_clear_model (GtkListView *self)
   g_clear_object (&self->model);
 }
 
-static void
-gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment,
-                                           GtkListView   *self)
-{
-  if (adjustment == self->adjustment[GTK_ORIENTATION_VERTICAL])
-    {
-      ListRow *row;
-      guint pos;
-      int dy;
-
-      row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy);
-      if (row)
-        pos = list_row_get_position (self, row);
-      else
-        pos = 0;
-
-      if (pos != self->anchor_pos || dy != self->anchor_dy)
-        {
-          self->anchor_pos = pos;
-          self->anchor_dy = dy;
-          gtk_widget_queue_allocate (GTK_WIDGET (self));
-        }
-    }
-  else
-    { 
-      gtk_widget_queue_allocate (GTK_WIDGET (self));
-    }
-}
-
 static void
 gtk_list_view_clear_adjustment (GtkListView    *self,
                                 GtkOrientation  orientation)
@@ -866,6 +898,7 @@ gtk_list_view_set_model (GtkListView *self,
                         self);
 
       gtk_list_view_add_rows (self, NULL, 0, g_list_model_get_n_items (model));
+      gtk_list_view_set_anchor (self, 0, 0);
     }
 
   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
@@ -879,13 +912,15 @@ gtk_list_view_set_functions (GtkListView          *self,
                              GDestroyNotify        user_destroy)
 {
   GtkListItemFactory *factory;
-  guint n_items;
+  guint n_items, anchor;
+  double anchor_align;
 
   g_return_if_fail (GTK_IS_LIST_VIEW (self));
   g_return_if_fail (setup_func || bind_func);
   g_return_if_fail (user_data != NULL || user_destroy == NULL);
 
   n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
+  anchor = gtk_list_view_get_anchor (self, &anchor_align);
   gtk_list_view_remove_rows (self, NULL, 0, n_items);
 
   factory = gtk_list_item_factory_new (setup_func, bind_func, user_data, user_destroy);
@@ -893,5 +928,6 @@ gtk_list_view_set_functions (GtkListView          *self,
   g_object_unref (factory);
 
   gtk_list_view_add_rows (self, NULL, 0, n_items);
+  gtk_list_view_set_anchor (self, anchor, anchor_align);
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]