[gtk/wip/otte/listview: 94/146] listview: Port various gridview improvements



commit fdf83c10c50382ecea0347b6d9e6d71af0a42da2
Author: Benjamin Otte <otte redhat com>
Date:   Tue Oct 22 03:21:39 2019 +0200

    listview: Port various gridview improvements
    
    - Handle anchor as align + top/bottom
      This fixes behavior for cells that are higher than the view
    - Add gtk_list_view_adjustment_is_flipped()
      This should fix RTL handling of horizontal lists
    - Fix scrolling
      This should make scrolling more reliable, particularly on short lists
      that are only a few pages long.

 gtk/gtklistview.c | 346 ++++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 255 insertions(+), 91 deletions(-)
---
diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
index c07f6d8368..d7886f957d 100644
--- a/gtk/gtklistview.c
+++ b/gtk/gtklistview.c
@@ -72,6 +72,7 @@ struct _GtkListView
 
   GtkListItemTracker *anchor;
   double anchor_align;
+  gboolean anchor_start;
   /* the last item that was selected - basically the location to extend selections from */
   GtkListItemTracker *selected;
   /* the item that has input focus */
@@ -273,6 +274,37 @@ list_row_get_y (GtkListView *self,
   return y ;
 }
 
+static gboolean
+gtk_list_view_get_size_at_position (GtkListView *self,
+                                    guint        pos,
+                                    int         *offset,
+                                    int         *height)
+{
+  ListRow *row;
+  guint skip;
+  int y;
+
+  row = gtk_list_item_manager_get_nth (self->item_manager, pos, &skip);
+  if (row == NULL)
+    {
+      if (offset)
+        *offset = 0;
+      if (height)
+        *height = 0;
+      return FALSE;
+    }
+
+  y = list_row_get_y (self, row);
+  y += skip * row->height;
+
+  if (offset)
+    *offset = y;
+  if (height)
+    *height = row->height;
+
+  return TRUE;
+}
+
 static int
 gtk_list_view_get_list_height (GtkListView *self)
 {
@@ -290,81 +322,146 @@ gtk_list_view_get_list_height (GtkListView *self)
 static void
 gtk_list_view_set_anchor (GtkListView *self,
                           guint        position,
-                          double       align)
+                          double       align,
+                          gboolean     start)
 {
   gtk_list_item_tracker_set_position (self->item_manager,
                                       self->anchor,
                                       position,
                                       GTK_LIST_VIEW_EXTRA_ITEMS + GTK_LIST_VIEW_MAX_LIST_ITEMS * align,
                                       GTK_LIST_VIEW_EXTRA_ITEMS + GTK_LIST_VIEW_MAX_LIST_ITEMS - 1 - 
GTK_LIST_VIEW_MAX_LIST_ITEMS * align);
-  if (self->anchor_align != align)
+  if (self->anchor_align != align || self->anchor_start != start)
     {
       self->anchor_align = align;
+      self->anchor_start = start;
       gtk_widget_queue_allocate (GTK_WIDGET (self));
     }
 }
 
+static gboolean
+gtk_list_view_adjustment_is_flipped (GtkListView    *self,
+                                     GtkOrientation  orientation)
+{
+  if (orientation == GTK_ORIENTATION_VERTICAL)
+    return FALSE;
+
+  return gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
+}
+
 static void
 gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment,
                                            GtkListView   *self)
 {
   if (adjustment == self->adjustment[self->orientation])
     {
+      int page_size, total_size, value, from_start;
+      int row_start, row_end;
+      double align;
+      gboolean top;
       guint pos;
-      int dy;
 
-      pos = gtk_list_view_get_position_at_y (self, gtk_adjustment_get_value (adjustment), &dy, NULL);
-      g_return_if_fail (pos != GTK_INVALID_LIST_POSITION);
-      gtk_list_view_set_anchor (self, pos, 0);
+      page_size = gtk_adjustment_get_page_size (adjustment);
+      value = gtk_adjustment_get_value (adjustment);
+      total_size = gtk_adjustment_get_upper (adjustment);
+
+      if (gtk_list_view_adjustment_is_flipped (self, self->orientation))
+        value = total_size - page_size - value;
+
+      /* Compute how far down we've scrolled. That's the height
+       * we want to align to. */
+      align = (double) value / (total_size - page_size);
+      from_start = round (align * page_size);
+      
+      pos = gtk_list_view_get_position_at_y (self,
+                                             value + from_start,
+                                             &row_start, &row_end);
+      if (pos != GTK_INVALID_LIST_POSITION)
+        {
+          /* offset from value - which is where we wanna scroll to */
+          row_start = from_start - row_start;
+          row_end += row_start;
+
+          /* find an anchor that is in the visible area */
+          if (row_start > 0 && row_end < page_size)
+            top = from_start - row_start <= row_end - from_start;
+          else if (row_start > 0)
+            top = TRUE;
+          else if (row_end < page_size)
+            top = FALSE;
+          else
+            {
+              /* This is the case where the row occupies the whole visible area.
+               * It's also the only case where align will not end up in [0..1] */
+              top = from_start - row_start <= row_end - from_start;
+            }
+
+          /* Now compute the align so that when anchoring to the looked
+           * up row, the position is pixel-exact.
+           */
+          align = (double) (top ? row_start : row_end) / page_size;
+        }
+      else
+        {
+          /* Happens if we scroll down to the end - we will query
+           * exactly the pixel behind the last one we can get a row for.
+           * So take the last row. */
+          pos = g_list_model_get_n_items (self->model) - 1;
+          align = 1.0;
+          top = FALSE;
+        }
+
+      gtk_list_view_set_anchor (self, pos, align, top);
     }
   
   gtk_widget_queue_allocate (GTK_WIDGET (self));
 }
 
-static void
+static int 
 gtk_list_view_update_adjustments (GtkListView    *self,
                                   GtkOrientation  orientation)
 {
-  double upper, page_size, value;
+  int upper, page_size, value;
+
+  page_size = gtk_widget_get_size (GTK_WIDGET (self), orientation);
 
   if (orientation == self->orientation)
     {
-      ListRow *row;
-      guint anchor;
+      int offset, size;
+      guint anchor_pos;
 
-      if (self->orientation == GTK_ORIENTATION_VERTICAL)
-        page_size = gtk_widget_get_height (GTK_WIDGET (self));
-      else
-        page_size = gtk_widget_get_width (GTK_WIDGET (self));
       upper = gtk_list_view_get_list_height (self);
+      anchor_pos = gtk_list_item_tracker_get_position (self->item_manager, self->anchor);
 
-      anchor = gtk_list_item_tracker_get_position (self->item_manager, self->anchor);
-      row = gtk_list_item_manager_get_nth (self->item_manager, anchor, NULL);
-      if (row)
-        value = list_row_get_y (self, row);
-      else
-        value = 0;
-      value -= self->anchor_align * (page_size - (row ? row->height : 0));
+      if (!gtk_list_view_get_size_at_position (self,
+                                               anchor_pos,
+                                               &offset,
+                                               &size))
+        {
+          g_assert_not_reached ();
+        }
+      if (!self->anchor_start)
+        offset += size;
+
+      value = offset - self->anchor_align * page_size;
     }
   else
     {
-      if (self->orientation == GTK_ORIENTATION_VERTICAL)
-        page_size = gtk_widget_get_width (GTK_WIDGET (self));
-      else
-        page_size = gtk_widget_get_height (GTK_WIDGET (self));
       upper = self->list_width;
-      value = 0;
-      if (_gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
-        value = upper - page_size - value;
       value = gtk_adjustment_get_value (self->adjustment[orientation]);
+      if (gtk_list_view_adjustment_is_flipped (self, orientation))
+        value = upper - value - page_size;
     }
   upper = MAX (upper, page_size);
+  value = MAX (value, 0);
+  value = MIN (value, 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,
+                            gtk_list_view_adjustment_is_flipped (self, orientation)
+                              ? upper - page_size - value
+                              : value,
                             0,
                             upper,
                             page_size * 0.1,
@@ -373,6 +470,8 @@ gtk_list_view_update_adjustments (GtkListView    *self,
   g_signal_handlers_unblock_by_func (self->adjustment[orientation],
                                      gtk_list_view_adjustment_value_changed_cb,
                                      self);
+
+  return value;
 }
 
 static int
@@ -498,6 +597,43 @@ gtk_list_view_measure (GtkWidget      *widget,
     gtk_list_view_measure_across (widget, orientation, for_size, minimum, natural);
 }
 
+static void
+gtk_list_view_size_allocate_child (GtkListView *self,
+                                   GtkWidget   *child,
+                                   int          x,
+                                   int          y,
+                                   int          width,
+                                   int          height)
+{
+  GtkAllocation child_allocation;
+
+  if (self->orientation == GTK_ORIENTATION_VERTICAL)
+    {
+      child_allocation.x = x;
+      child_allocation.y = y;
+      child_allocation.width = width;
+      child_allocation.height = height;
+    }
+  else if (_gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_LTR)
+    {
+      child_allocation.x = y;
+      child_allocation.y = x;
+      child_allocation.width = height;
+      child_allocation.height = width;
+    }
+  else
+    {
+      int mirror_point = gtk_widget_get_width (GTK_WIDGET (self));
+
+      child_allocation.x = mirror_point - y - height; 
+      child_allocation.y = x;
+      child_allocation.width = height;
+      child_allocation.height = width;
+    }
+
+  gtk_widget_size_allocate (child, &child_allocation, -1);
+}
+
 static void
 gtk_list_view_size_allocate (GtkWidget *widget,
                              int        width,
@@ -505,10 +641,10 @@ gtk_list_view_size_allocate (GtkWidget *widget,
                              int        baseline)
 {
   GtkListView *self = GTK_LIST_VIEW (widget);
-  GtkAllocation child_allocation = { 0, 0, 0, 0 };
   ListRow *row;
   GArray *heights;
   int min, nat, row_height;
+  int x, y;
   GtkOrientation opposite_orientation;
 
   opposite_orientation = OPPOSITE_ORIENTATION (self->orientation);
@@ -571,43 +707,26 @@ gtk_list_view_size_allocate (GtkWidget *widget,
     }
 
   /* step 3: update the adjustments */
-  gtk_list_view_update_adjustments (self, GTK_ORIENTATION_HORIZONTAL);
-  gtk_list_view_update_adjustments (self, GTK_ORIENTATION_VERTICAL);
+  x = - gtk_list_view_update_adjustments (self, opposite_orientation);
+  y = - gtk_list_view_update_adjustments (self, self->orientation);
 
   /* step 4: actually allocate the widgets */
-  child_allocation.x = - round (gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_HORIZONTAL]));
-  child_allocation.y = - round (gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_VERTICAL]));
-  if (self->orientation == GTK_ORIENTATION_VERTICAL)
-    {
-      child_allocation.width = self->list_width;
-      for (row = gtk_list_item_manager_get_first (self->item_manager);
-           row != NULL;
-           row = gtk_rb_tree_node_get_next (row))
-        {
-          if (row->parent.widget)
-            {
-              child_allocation.height = row->height;
-              gtk_widget_size_allocate (row->parent.widget, &child_allocation, -1);
-            }
 
-          child_allocation.y += row->height * row->parent.n_items;
-        }
-    }
-  else
+  for (row = gtk_list_item_manager_get_first (self->item_manager);
+       row != NULL;
+       row = gtk_rb_tree_node_get_next (row))
     {
-      child_allocation.height = self->list_width;
-      for (row = gtk_list_item_manager_get_first (self->item_manager);
-           row != NULL;
-           row = gtk_rb_tree_node_get_next (row))
+      if (row->parent.widget)
         {
-          if (row->parent.widget)
-            {
-              child_allocation.width = row->height;
-              gtk_widget_size_allocate (row->parent.widget, &child_allocation, -1);
-            }
-
-          child_allocation.x += row->height * row->parent.n_items;
+          gtk_list_view_size_allocate_child (self,
+                                             row->parent.widget,
+                                             x,
+                                             y,
+                                             self->list_width,
+                                             row->height);
         }
+
+      y += row->height * row->parent.n_items;
     }
 }
 
@@ -946,50 +1065,95 @@ gtk_list_view_update_focus_tracker (GtkListView *self)
     }
 }
 
+static void
+gtk_list_view_compute_scroll_align (GtkListView    *self,
+                                    GtkOrientation  orientation,
+                                    int             cell_start,
+                                    int             cell_end,
+                                    double          current_align,
+                                    gboolean        current_start,
+                                    double         *new_align,
+                                    gboolean       *new_start)
+{
+  int visible_start, visible_size, visible_end;
+  int cell_size;
+
+  visible_start = gtk_adjustment_get_value (self->adjustment[orientation]);
+  visible_size = gtk_adjustment_get_page_size (self->adjustment[orientation]);
+  if (gtk_list_view_adjustment_is_flipped (self, orientation))
+    visible_start = gtk_adjustment_get_upper (self->adjustment[orientation]) - visible_size - visible_start;
+  visible_end = visible_start + visible_size;
+  cell_size = cell_end - cell_start;
+
+  if (cell_size <= visible_size)
+    {
+      if (cell_start < visible_start)
+        {
+          *new_align = 0.0;
+          *new_start = TRUE;
+        }
+      else if (cell_end > visible_end)
+        {
+          *new_align = 1.0;
+          *new_start = FALSE;
+        }
+      else
+        {
+          /* XXX: start or end here? */
+          *new_start = TRUE;
+          *new_align = (double) (cell_start - visible_start) / visible_size;
+        }
+    }
+  else
+    {
+      /* This is the unlikely case of the cell being higher than the visible area */
+      if (cell_start > visible_start)
+        {
+          *new_align = 0.0;
+          *new_start = TRUE;
+        }
+      else if (cell_end < visible_end)
+        {
+          *new_align = 1.0;
+          *new_start = FALSE;
+        }
+      else
+        {
+          /* the cell already covers the whole screen */
+          *new_align = current_align;
+          *new_start = current_start;
+        }
+    }
+}
+
 static void
 gtk_list_view_scroll_to_item (GtkWidget  *widget,
                               const char *action_name,
                               GVariant   *parameter)
 {
   GtkListView *self = GTK_LIST_VIEW (widget);
-  ListRow *row;
+  int start, end;
+  double align;
+  gboolean top;
   guint pos;
 
   if (!g_variant_check_format_string (parameter, "u", FALSE))
     return;
 
   g_variant_get (parameter, "u", &pos);
-  row = gtk_list_item_manager_get_nth (self->item_manager, pos, NULL);
-  if (row == NULL)
-    return;
 
-  if (row->parent.widget)
-    {
-      int y = list_row_get_y (self, row);
-      int start = gtk_adjustment_get_value (self->adjustment[self->orientation]);
-      int height;
-      double align;
+  /* figure out primary orientation and if position is valid */
+  if (!gtk_list_view_get_size_at_position (self, pos, &start, &end))
+    return;
 
-      if (self->orientation == GTK_ORIENTATION_VERTICAL)
-        height = gtk_widget_get_height (GTK_WIDGET (self));
-      else
-        height = gtk_widget_get_width (GTK_WIDGET (self));
-      if (y < start)
-        align = 0.0;
-      else if (y + row->height > start + height)
-        align = 1.0;
-      else
-        align = (double) (y - start) / (height - row->height);
+  end += start;
+  gtk_list_view_compute_scroll_align (self,
+                                      self->orientation,
+                                      start, end,
+                                      self->anchor_align, self->anchor_start,
+                                      &align, &top);
 
-      gtk_list_view_set_anchor (self, pos, align);
-    }
-  else
-    {
-      if (pos < gtk_list_item_tracker_get_position (self->item_manager, self->anchor))
-        gtk_list_view_set_anchor (self, pos, 0.0);
-      else
-        gtk_list_view_set_anchor (self, pos, 1.0);
-    }
+  gtk_list_view_set_anchor (self, pos, align, top);
 
   /* HACK HACK HACK
    *
@@ -1596,7 +1760,7 @@ gtk_list_view_set_model (GtkListView *self,
         selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (model));
 
       gtk_list_item_manager_set_model (self->item_manager, selection_model);
-      gtk_list_view_set_anchor (self, 0, 0.0);
+      gtk_list_view_set_anchor (self, 0, 0.0, TRUE);
 
       g_object_unref (selection_model);
     }


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