[gtk/wip/otte/listview: 225/309] gridview: Actually do something



commit 96c6246786d22e1d886bea69aed81c17d0b69ae1
Author: Benjamin Otte <otte redhat com>
Date:   Sat Oct 12 22:53:13 2019 +0200

    gridview: Actually do something
    
    Implement measuring and allocating items - which makes the items appear
    when drawing and allows interacting with the items.
    
    However, the gridview still does not allow any user interaction
    (including scrolling).

 gtk/gtkgridview.c | 515 +++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 469 insertions(+), 46 deletions(-)
---
diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c
index 3f3ecc72da..9972d19a6b 100644
--- a/gtk/gtkgridview.c
+++ b/gtk/gtkgridview.c
@@ -28,7 +28,18 @@
 #include "gtkorientableprivate.h"
 #include "gtkprivate.h"
 #include "gtkscrollable.h"
+#include "gtksingleselection.h"
 #include "gtktypebuiltins.h"
+#include "gtkwidgetprivate.h"
+
+/* Maximum number of list items created by the gridview.
+ * For debugging, you can set this to G_MAXUINT to ensure
+ * there's always a list item for every row.
+ *
+ * We multiply this number with GtkGridView:max-columns so
+ * that we can always display at least this many rows.
+ */
+#define GTK_GRID_VIEW_MIN_VISIBLE_ROWS (30)
 
 #define DEFAULT_MAX_COLUMNS (7)
 
@@ -55,22 +66,24 @@ struct _GtkGridView
   GtkOrientation orientation;
   guint min_columns;
   guint max_columns;
+  /* set in size_allocate */
+  guint n_columns;
+  double column_width;
+
+  GtkListItemTracker *anchor;
+  double anchor_align;
 };
 
 struct _Cell
 {
   GtkListItemManagerItem parent;
-  guint size_first_row; /* total */
-  guint size; /* total */
-  guint size_last_row; /* total */
+  guint size; /* total, only counting cells in first column */
 };
 
 struct _CellAugment
 {
   GtkListItemManagerItemAugment parent;
-  guint size_first_row; /* total */
-  guint size; /* total */
-  guint size_last_row; /* total */
+  guint size; /* total, only counting first column */
 };
 
 enum
@@ -95,6 +108,50 @@ G_DEFINE_TYPE_WITH_CODE (GtkGridView, gtk_grid_view, GTK_TYPE_WIDGET,
 
 static GParamSpec *properties[N_PROPS] = { NULL, };
 
+static void G_GNUC_UNUSED
+dump (GtkGridView *self)
+{
+  Cell *cell;
+  guint n_widgets, n_list_rows, n_items;
+
+  n_widgets = 0;
+  n_list_rows = 0;
+  n_items = 0;
+  //g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end);
+  for (cell = gtk_list_item_manager_get_first (self->item_manager);
+       cell;
+       cell = gtk_rb_tree_node_get_next (cell))
+    {
+      if (cell->parent.widget)
+        n_widgets++;
+      n_list_rows++;
+      n_items += cell->parent.n_items;
+      g_print ("%6u%6u %5ux%3u %s (%upx)\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);
+    }
+
+  g_print ("  => %u widgets in %u list rows\n", n_widgets, n_list_rows);
+}
+static void
+gtk_grid_view_set_anchor (GtkGridView *self,
+                          guint        position,
+                          double       align)
+{
+  gtk_list_item_tracker_set_position (self->item_manager,
+                                      self->anchor,
+                                      0,
+                                      (GTK_GRID_VIEW_MIN_VISIBLE_ROWS * align + 1) * self->max_columns,
+                                      (GTK_GRID_VIEW_MIN_VISIBLE_ROWS * (1 - align) + 1) * 
self->max_columns);
+  if (self->anchor_align != align)
+    {
+      self->anchor_align = align;
+      gtk_widget_queue_allocate (GTK_WIDGET (self));
+    }
+}
+
 static void
 gtk_grid_view_adjustment_value_changed_cb (GtkAdjustment *adjustment,
                                            GtkGridView   *self)
@@ -102,10 +159,186 @@ gtk_grid_view_adjustment_value_changed_cb (GtkAdjustment *adjustment,
   gtk_widget_queue_allocate (GTK_WIDGET (self));
 }
 
-static gboolean
-gtk_grid_view_is_empty (GtkGridView *self)
+static void
+gtk_grid_view_update_adjustments (GtkGridView    *self,
+                                  GtkOrientation  orientation)
+{
+  g_signal_handlers_block_by_func (self->adjustment[orientation],
+                                   gtk_grid_view_adjustment_value_changed_cb,
+                                   self);
+  gtk_adjustment_configure (self->adjustment[orientation],
+                            0,
+                            0,
+                            0,
+                            0,
+                            0,
+                            0);
+  g_signal_handlers_unblock_by_func (self->adjustment[orientation],
+                                     gtk_grid_view_adjustment_value_changed_cb,
+                                     self);
+}
+
+static int
+compare_ints (gconstpointer first,
+              gconstpointer second)
+{
+  return *(int *) first - *(int *) second;
+}
+
+static int
+gtk_grid_view_get_unknown_row_size (GtkGridView *self,
+                                    GArray      *heights)
+{
+  g_return_val_if_fail (heights->len > 0, 0);
+
+  /* return the median and hope rows are generally uniform with few outliers */
+  g_array_sort (heights, compare_ints);
+
+  return g_array_index (heights, int, heights->len / 2);
+}
+
+static void
+gtk_grid_view_measure_column_size (GtkGridView *self,
+                                   int         *minimum,
+                                   int         *natural)
 {
-  return self->model == NULL;
+  GtkOrientation opposite;
+  Cell *cell;
+  int min, nat, child_min, child_nat;
+
+  min = 0;
+  nat = 0;
+  opposite = OPPOSITE_ORIENTATION (self->orientation);
+
+  for (cell = gtk_list_item_manager_get_first (self->item_manager);
+       cell != NULL;
+       cell = gtk_rb_tree_node_get_next (cell))
+    {
+      /* ignore unavailable cells */
+      if (cell->parent.widget == NULL)
+        continue;
+
+      gtk_widget_measure (cell->parent.widget,
+                          opposite, -1,
+                          &child_min, &child_nat, NULL, NULL);
+      min = MAX (min, child_min);
+      nat = MAX (nat, child_nat);
+    }
+
+  *minimum = min;
+  *natural = nat;
+}
+
+static void
+gtk_grid_view_measure_across (GtkWidget *widget,
+                              int        for_size,
+                              int       *minimum,
+                              int       *natural)
+{
+  GtkGridView *self = GTK_GRID_VIEW (widget);
+
+  gtk_grid_view_measure_column_size (self, minimum, natural);
+
+  *minimum *= self->min_columns;
+  *natural *= self->max_columns;
+}
+
+static guint
+gtk_grid_view_compute_n_columns (GtkGridView *self,
+                                 guint        for_size,
+                                 int          min,
+                                 int          nat)
+{
+  guint n_columns;
+
+  /* rounding down is exactly what we want here, so int division works */
+  if (self->scroll_policy[OPPOSITE_ORIENTATION (self->orientation)] == GTK_SCROLL_MINIMUM)
+    n_columns = for_size / MAX (1, min);
+  else
+    n_columns = for_size / MAX (1, nat);
+
+  n_columns = CLAMP (n_columns, self->min_columns, self->max_columns);
+
+  return n_columns;
+}
+
+static void
+gtk_grid_view_measure_list (GtkWidget *widget,
+                            int        for_size,
+                            int       *minimum,
+                            int       *natural)
+{
+  GtkGridView *self = GTK_GRID_VIEW (widget);
+  Cell *cell;
+  int height, row_height, child_min, child_nat, column_size, col_min, col_nat;
+  gboolean measured;
+  GArray *heights;
+  guint n_unknown, n_columns;
+  guint i;
+
+  heights = g_array_new (FALSE, FALSE, sizeof (int));
+  n_unknown = 0;
+  height = 0;
+
+  gtk_grid_view_measure_column_size (self, &col_min, &col_nat);
+  for_size = MAX (for_size, col_min * (int) self->min_columns);
+  n_columns = gtk_grid_view_compute_n_columns (self, for_size, col_min, col_nat);
+  column_size = for_size / n_columns;
+
+  i = 0;
+  row_height = 0;
+  measured = FALSE;
+  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)
+        {
+          gtk_widget_measure (cell->parent.widget,
+                              self->orientation, column_size,
+                              &child_min, &child_nat, NULL, NULL);
+          if (self->scroll_policy[self->orientation] == GTK_SCROLL_MINIMUM)
+            row_height = MAX (row_height, child_min);
+          else
+            row_height = MAX (row_height, child_nat);
+          measured = TRUE;
+        }
+      
+      i += cell->parent.n_items;
+
+      if (i >= n_columns)
+        {
+          if (measured)
+            {
+              g_array_append_val (heights, row_height);
+              i -= n_columns;
+              height += row_height;
+              measured = FALSE;
+              row_height = 0;
+            }
+          n_unknown += i / n_columns;
+          i %= n_columns;
+        }
+    }
+
+  if (i > 0)
+    {
+      if (measured)
+        {
+          g_array_append_val (heights, row_height);
+          height += row_height;
+        }
+      else
+        n_unknown++;
+    }
+
+  if (n_unknown)
+    height += n_unknown * gtk_grid_view_get_unknown_row_size (self, heights);
+
+  g_array_free (heights, TRUE);
+
+  *minimum = height;
+  *natural = height;
 }
 
 static void
@@ -119,44 +352,215 @@ gtk_grid_view_measure (GtkWidget      *widget,
 {
   GtkGridView *self = GTK_GRID_VIEW (widget);
 
-  if (gtk_grid_view_is_empty (self))
-    {
-      *minimum = 0;
-      *natural = 0;
-      return;
-    }
-
-  *minimum = 0;
-  *natural = 0;
-  return;
+  if (orientation == self->orientation)
+    gtk_grid_view_measure_list (widget, for_size, minimum, natural);
+  else
+    gtk_grid_view_measure_across (widget, for_size, minimum, natural);
 }
 
 static void
-gtk_grid_view_size_allocate (GtkWidget *widget,
-                             int        width,
-                             int        height,
-                             int        baseline)
+cell_set_size (Cell  *cell,
+               guint  size)
 {
-  //GtkGridView *self = GTK_GRID_VIEW (widget);
+  if (cell->size == size)
+    return;
+
+  cell->size = size;
+  gtk_rb_tree_node_mark_dirty (cell);
 }
 
 static void
-gtk_grid_view_model_items_changed_cb (GListModel  *model,
-                                      guint        position,
-                                      guint        removed,
-                                      guint        added,
-                                      GtkGridView *self)
+gtk_grid_view_size_allocate_child (GtkGridView *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_grid_view_clear_model (GtkGridView *self)
+gtk_grid_view_size_allocate (GtkWidget *widget,
+                             int        width,
+                             int        height,
+                             int        baseline)
 {
-  if (self->model == NULL)
+  GtkGridView *self = GTK_GRID_VIEW (widget);
+  Cell *cell, *start;
+  GArray *heights;
+  int unknown_height, row_height, col_min, col_nat;
+  GtkOrientation opposite_orientation;
+  gboolean known;
+  int x, y;
+  guint i;
+
+  opposite_orientation = OPPOSITE_ORIENTATION (self->orientation);
+
+  /* step 0: exit early if list is empty */
+  if (gtk_list_item_manager_get_root (self->item_manager) == NULL)
     return;
 
-  g_signal_handlers_disconnect_by_func (self->model, gtk_grid_view_model_items_changed_cb, self);
-  g_clear_object (&self->model);
+  /* step 1: 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, 
+                                                     self->orientation == GTK_ORIENTATION_VERTICAL ? width : 
height,
+                                                     col_min, col_nat);
+  self->column_width = (self->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 */
+  heights = g_array_new (FALSE, FALSE, sizeof (int));
+
+  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, self->orientation,
+                              self->column_width,
+                              &min, &nat, NULL, NULL);
+          if (self->scroll_policy[self->orientation] == GTK_SCROLL_MINIMUM)
+            size = min;
+          else
+            size = nat;
+          g_array_append_val (heights, size);
+          row_height = MAX (row_height, size);
+        }
+      cell_set_size (cell, 0);
+      i += cell->parent.n_items;
+
+      if (i >= self->n_columns)
+        {
+          i %= self->n_columns;
+
+          cell_set_size (start, start->size + row_height);
+          start = cell;
+          row_height = 0;
+        }
+    }
+  if (i > 0)
+    cell_set_size (start, start->size + row_height);
+
+  /* step 3: determine height of rows with only unknown items */
+  unknown_height = gtk_grid_view_get_unknown_row_size (self, heights);
+  g_array_free (heights, TRUE);
+
+  i = 0;
+  known = FALSE;
+  for (start = 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;
+
+      i += cell->parent.n_items;
+      if (i >= self->n_columns)
+        {
+          if (!known)
+            cell_set_size (start, start->size + unknown_height);
+
+          i -= self->n_columns;
+          known = FALSE;
+
+          if (i >= self->n_columns)
+            {
+              cell_set_size (cell, cell->size + unknown_height * (i / self->n_columns));
+              i %= self->n_columns;
+            }
+          start = cell;
+        }
+    }
+  if (i > 0 && !known)
+    cell_set_size (start, start->size + unknown_height);
+
+  /* step 4: update the adjustments */
+  gtk_grid_view_update_adjustments (self, GTK_ORIENTATION_HORIZONTAL);
+  gtk_grid_view_update_adjustments (self, GTK_ORIENTATION_VERTICAL);
+
+  /* step 5: actually allocate the widgets */
+  x = - round (gtk_adjustment_get_value (self->adjustment[opposite_orientation]));
+  y = - round (gtk_adjustment_get_value (self->adjustment[self->orientation]));
+
+  i = 0;
+  row_height = 0;
+  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)
+        {
+          if (i == 0)
+            {
+              y += row_height;
+              row_height = cell->size;
+            }
+          gtk_grid_view_size_allocate_child (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 = (i + 1) % self->n_columns;
+        }
+      else
+        {
+          i += cell->parent.n_items;
+          if (i > self->n_columns)
+            {
+              i -= self->n_columns;
+              y += row_height;
+              row_height = cell->size;
+
+              if (i > self->n_columns)
+                {
+                  guint unknown_rows = (i - 1) / self->n_columns;
+                  int unknown_height2 = unknown_rows * unknown_height;
+                  row_height -= unknown_height2;
+                  y += unknown_height2;
+                  i %= self->n_columns;
+                }
+            }
+        }
+    }
 }
 
 static void
@@ -177,11 +581,16 @@ gtk_grid_view_dispose (GObject *object)
 {
   GtkGridView *self = GTK_GRID_VIEW (object);
 
-  gtk_grid_view_clear_model (self);
+  g_clear_object (&self->model);
 
   gtk_grid_view_clear_adjustment (self, GTK_ORIENTATION_HORIZONTAL);
   gtk_grid_view_clear_adjustment (self, GTK_ORIENTATION_VERTICAL);
 
+  if (self->anchor)
+    {
+      gtk_list_item_tracker_free (self->item_manager, self->anchor);
+      self->anchor = NULL;
+    }
   g_clear_object (&self->item_manager);
 
   G_OBJECT_CLASS (gtk_grid_view_parent_class)->dispose (object);
@@ -449,34 +858,33 @@ cell_augment (GtkRbTree *tree,
               gpointer   left,
               gpointer   right)
 {
-#if 0
   Cell *cell = node;
   CellAugment *aug = node_augment;
 
   gtk_list_item_manager_augment_node (tree, node_augment, node, left, right);
 
-  aug->height = row->height * row->parent.n_items;
+  aug->size = cell->size;
 
   if (left)
     {
-      ListRowAugment *left_aug = gtk_rb_tree_get_augment (tree, left);
+      CellAugment *left_aug = gtk_rb_tree_get_augment (tree, left);
 
-      aug->height += left_aug->height;
+      aug->size += left_aug->size;
     }
 
   if (right)
     {
-      ListRowAugment *right_aug = gtk_rb_tree_get_augment (tree, right);
+      CellAugment *right_aug = gtk_rb_tree_get_augment (tree, right);
 
-      aug->height += right_aug->height;
+      aug->size += right_aug->size;
     }
-#endif
 }
 
 static void
 gtk_grid_view_init (GtkGridView *self)
 {
   self->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self), "flowboxchild", Cell, CellAugment, 
cell_augment);
+  self->anchor = gtk_list_item_tracker_new (self->item_manager);
 
   self->min_columns = 1;
   self->max_columns = DEFAULT_MAX_COLUMNS;
@@ -573,16 +981,27 @@ gtk_grid_view_set_model (GtkGridView *self,
   if (self->model == model)
     return;
 
-  gtk_grid_view_clear_model (self);
+  g_clear_object (&self->model);
 
   if (model)
     {
+      GtkSelectionModel *selection_model;
+
       self->model = g_object_ref (model);
 
-      g_signal_connect (model,
-                        "items-changed", 
-                        G_CALLBACK (gtk_grid_view_model_items_changed_cb),
-                        self);
+      if (GTK_IS_SELECTION_MODEL (model))
+        selection_model = GTK_SELECTION_MODEL (g_object_ref (model));
+      else
+        selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (model));
+
+      gtk_list_item_manager_set_model (self->item_manager, selection_model);
+      gtk_grid_view_set_anchor (self, 0, 0.0);
+
+      g_object_unref (selection_model);
+    }
+  else
+    {
+      gtk_list_item_manager_set_model (self->item_manager, NULL);
     }
 
   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
@@ -664,6 +1083,10 @@ gtk_grid_view_set_max_columns (GtkGridView *self,
 
   self->max_columns = max_columns;
 
+  gtk_grid_view_set_anchor (self,
+                            gtk_list_item_tracker_get_position (self->item_manager, self->anchor),
+                            self->anchor_align);
+
   gtk_widget_queue_resize (GTK_WIDGET (self));
 
   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_COLUMNS]);


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