listview: Support CSS border-spacing
authorBenjamin Otte <otte@redhat.com>
Thu, 9 Mar 2023 03:43:51 +0000 (04:43 +0100)
committerBenjamin Otte <otte@redhat.com>
Fri, 10 Mar 2023 16:03:21 +0000 (17:03 +0100)
Hopefully the code can deal with gaps between the tiles.

We use gtk_list_item_manager_get_nearest_tile() when necessary.

gtk/gtklistbase.c
gtk/gtklistbaseprivate.h
gtk/gtklistview.c

index 7b781bf3b3c412b5746cad40518347995231369e..81ac56cc6ff997a569d8d05204b095f31b740a9d 100644 (file)
@@ -23,6 +23,8 @@
 
 #include "gtkadjustment.h"
 #include "gtkbitset.h"
+#include "gtkcssnodeprivate.h"
+#include "gtkcsspositionvalueprivate.h"
 #include "gtkdragsourceprivate.h"
 #include "gtkdropcontrollermotion.h"
 #include "gtkgesturedrag.h"
@@ -1995,6 +1997,30 @@ gtk_list_base_get_orientation (GtkListBase *self)
   return priv->orientation;
 }
 
+void
+gtk_list_base_get_border_spacing (GtkListBase *self,
+                                  int         *xspacing,
+                                  int         *yspacing)
+{
+  GtkCssStyle *style = gtk_css_node_get_style (gtk_widget_get_css_node (GTK_WIDGET (self)));
+  GtkCssValue *border_spacing = style->size->border_spacing;
+
+  if (gtk_list_base_get_orientation (self) == GTK_ORIENTATION_HORIZONTAL)
+    {
+      if (xspacing)
+        *xspacing = _gtk_css_position_value_get_y (border_spacing, 0);
+      if (yspacing)
+        *yspacing = _gtk_css_position_value_get_x (border_spacing, 0);
+    }
+  else
+    {
+      if (xspacing)
+        *xspacing = _gtk_css_position_value_get_x (border_spacing, 0);
+      if (yspacing)
+        *yspacing = _gtk_css_position_value_get_y (border_spacing, 0);
+    }
+}
+
 GtkListItemManager *
 gtk_list_base_get_manager (GtkListBase *self)
 {
index c79bd8b13d2709c46819d23a761491e0b22b7a8b..e8ab5b5db791006bd24c53875ca68b40fa8a2e36 100644 (file)
@@ -62,6 +62,9 @@ struct _GtkListBaseClass
 GtkOrientation         gtk_list_base_get_orientation            (GtkListBase            *self);
 #define gtk_list_base_get_opposite_orientation(self) OPPOSITE_ORIENTATION(gtk_list_base_get_orientation(self))
 guint                  gtk_list_base_get_focus_position         (GtkListBase            *self);
+void                   gtk_list_base_get_border_spacing         (GtkListBase            *self,
+                                                                 int                    *xspacing,
+                                                                 int                    *yspacing);
 GtkListItemManager *   gtk_list_base_get_manager                (GtkListBase            *self);
 GtkScrollablePolicy    gtk_list_base_get_scroll_policy          (GtkListBase            *self,
                                                                  GtkOrientation          orientation);
index f210ac26cef4b87af7eea1ae94e0fa1a6fe7948c..f822c53529af2079d9ecb362813f0b831f14ccfd 100644 (file)
@@ -193,22 +193,23 @@ gtk_list_view_split (GtkListBase *base,
 {
   GtkListView *self = GTK_LIST_VIEW (base);
   GtkListTile *new_tile;
-  guint row_height;
+  int spacing, row_height;
 
-  row_height = tile->area.height / tile->n_items;
+  gtk_list_base_get_border_spacing (GTK_LIST_BASE (self), NULL, &spacing);
+  row_height = (tile->area.height - (tile->n_items - 1) * spacing) / tile->n_items;
 
   new_tile = gtk_list_tile_split (self->item_manager, tile, n_items);
   gtk_list_tile_set_area_size (self->item_manager,
                                tile,
                                tile->area.width,
-                               row_height * tile->n_items);
+                               row_height * tile->n_items + spacing * (tile->n_items - 1));
   gtk_list_tile_set_area (self->item_manager,
                           new_tile,
                           &(GdkRectangle) {
                             tile->area.x,
-                            tile->area.y + tile->area.height,
+                            tile->area.y + tile->area.height + spacing,
                             tile->area.width,
-                            row_height * new_tile->n_items
+                            row_height * new_tile->n_items + spacing * (new_tile->n_items - 1)
                           });
 
   return new_tile;
@@ -239,6 +240,9 @@ gtk_list_view_get_allocation (GtkListBase  *base,
     {
       /* item is not allocated yet */
       GtkListTile *other;
+      int spacing;
+
+      gtk_list_base_get_border_spacing (GTK_LIST_BASE (self), NULL, &spacing);
 
       for (other = gtk_rb_tree_node_get_previous (tile);
            other;
@@ -248,7 +252,7 @@ gtk_list_view_get_allocation (GtkListBase  *base,
             {
               area->x = other->area.x;
               area->width = other->area.width;
-              area->y = other->area.y + other->area.height;
+              area->y = other->area.y + other->area.height + spacing;
               break;
             }
         }
@@ -262,7 +266,7 @@ gtk_list_view_get_allocation (GtkListBase  *base,
                 {
                   area->x = other->area.x;
                   area->width = other->area.width;
-                  area->y = other->area.y;
+                  area->y = MAX (0, other->area.y - spacing);
                   break;
                 }
             }
@@ -272,36 +276,6 @@ gtk_list_view_get_allocation (GtkListBase  *base,
   return TRUE;
 }
 
-static GtkBitset *
-gtk_list_view_get_items_in_rect (GtkListBase                 *base,
-                                 const cairo_rectangle_int_t *rect)
-{
-  GtkListView *self = GTK_LIST_VIEW (base);
-  guint first, last, n_items;
-  GtkBitset *result;
-  GtkListTile *tile;
-
-  result = gtk_bitset_new_empty ();
-
-  n_items = gtk_list_base_get_n_items (base);
-  if (n_items == 0)
-    return result;
-
-  tile = gtk_list_item_manager_get_tile_at (self->item_manager, 0, rect->y);
-  if (tile)
-    first = gtk_list_tile_get_position (self->item_manager, tile);
-  else
-    first = rect->y < 0 ? 0 : n_items - 1;
-  tile = gtk_list_item_manager_get_tile_at (self->item_manager, 0, rect->y + rect->height);
-  if (tile)
-    last = gtk_list_tile_get_position (self->item_manager, tile);
-  else
-    last = rect->y + rect->height < 0 ? 0 : n_items - 1;
-
-  gtk_bitset_add_range_closed (result, first, last);
-  return result;
-}
-
 static guint
 gtk_list_view_move_focus_along (GtkListBase *base,
                                 guint        pos,
@@ -326,9 +300,8 @@ gtk_list_view_get_position_from_allocation (GtkListBase           *base,
 {
   GtkListView *self = GTK_LIST_VIEW (base);
   GtkListTile *tile;
-  int row_height, tile_pos;
 
-  tile = gtk_list_item_manager_get_tile_at (self->item_manager, x, y);
+  tile = gtk_list_item_manager_get_nearest_tile (self->item_manager, x, y);
   if (tile == NULL)
     return FALSE;
 
@@ -344,22 +317,60 @@ gtk_list_view_get_position_from_allocation (GtkListBase           *base,
     }
 
   *pos = gtk_list_tile_get_position (self->item_manager, tile);
-  row_height = (tile->area.height / tile->n_items);
-  tile_pos = (y - tile->area.y) / row_height;
-
-  *pos += tile_pos;
-
   if (area)
+    *area = tile->area;
+
+  if (tile->n_items > 1)
     {
-      area->x = tile->area.x;
-      area->width = tile->area.width;
-      area->y = tile->area.y + tile_pos * row_height;
-      area->height = row_height;
+      int row_height, tile_pos, spacing;
+
+      gtk_list_base_get_border_spacing (GTK_LIST_BASE (self), NULL, &spacing);
+      row_height = (tile->area.height - (tile->n_items - 1) * spacing) / tile->n_items;
+      if (y >= tile->area.y + tile->area.height)
+        tile_pos = tile->n_items - 1;
+      else
+        tile_pos = (y - tile->area.y) / (row_height + spacing);
+
+      *pos += tile_pos;
+      if (area)
+        {
+          area->y = tile->area.y + tile_pos * (row_height + spacing);
+          area->height = row_height;
+        }
     }
 
   return TRUE;
 }
 
+static GtkBitset *
+gtk_list_view_get_items_in_rect (GtkListBase                 *base,
+                                 const cairo_rectangle_int_t *rect)
+{
+  guint first, last;
+  cairo_rectangle_int_t area;
+  GtkBitset *result;
+
+  result = gtk_bitset_new_empty ();
+
+  if (!gtk_list_view_get_position_from_allocation (base, rect->x, rect->y, &first, &area))
+    return result;
+  if (area.y + area.height < rect->y)
+    first++;
+
+  if (!gtk_list_view_get_position_from_allocation (base,
+                                                   rect->x + rect->width - 1,
+                                                   rect->y + rect->height - 1,
+                                                   &last, &area))
+    return result;
+  if (area.y >= rect->y + rect->height)
+    last--;
+
+  if (last >= first)
+    gtk_bitset_add_range_closed (result, first, last);
+
+  return result;
+}
+
 static guint
 gtk_list_view_move_focus_across (GtkListBase *base,
                                  guint        pos,
@@ -432,9 +443,14 @@ gtk_list_view_measure_list (GtkWidget      *widget,
 {
   GtkListView *self = GTK_LIST_VIEW (widget);
   GtkListTile *tile;
-  int min, nat, child_min, child_nat;
+  int min, nat, child_min, child_nat, spacing;
   GArray *min_heights, *nat_heights;
-  guint n_unknown;
+  guint n_unknown, n_items;
+
+  n_items = gtk_list_base_get_n_items (GTK_LIST_BASE (self));
+  if (n_items == 0)
+    return;
+  gtk_list_base_get_border_spacing (GTK_LIST_BASE (self), NULL, &spacing);
 
   min_heights = g_array_new (FALSE, FALSE, sizeof (int));
   nat_heights = g_array_new (FALSE, FALSE, sizeof (int));
@@ -470,8 +486,8 @@ gtk_list_view_measure_list (GtkWidget      *widget,
   g_array_free (min_heights, TRUE);
   g_array_free (nat_heights, TRUE);
 
-  *minimum = min;
-  *natural = nat;
+  *minimum = min + spacing * (n_items - 1);
+  *natural = nat + spacing * (n_items - 1);
 }
 
 static void
@@ -500,7 +516,7 @@ gtk_list_view_size_allocate (GtkWidget *widget,
   GtkListView *self = GTK_LIST_VIEW (widget);
   GtkListTile *tile;
   GArray *heights;
-  int min, nat, row_height, y, list_width;
+  int min, nat, row_height, y, list_width, spacing;
   GtkOrientation orientation, opposite_orientation;
   GtkScrollablePolicy scroll_policy, opposite_scroll_policy;
 
@@ -508,6 +524,7 @@ gtk_list_view_size_allocate (GtkWidget *widget,
   opposite_orientation = OPPOSITE_ORIENTATION (orientation);
   scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), orientation);
   opposite_scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), opposite_orientation);
+  gtk_list_base_get_border_spacing (GTK_LIST_BASE (self), NULL, &spacing);
 
   /* step 0: exit early if list is empty */
   tile = gtk_list_tile_gc (self->item_manager, gtk_list_item_manager_get_first (self->item_manager));
@@ -559,9 +576,15 @@ gtk_list_view_size_allocate (GtkWidget *widget,
     {
       gtk_list_tile_set_area_position (self->item_manager, tile, 0, y);
       if (tile->widget == NULL)
-        gtk_list_tile_set_area_size (self->item_manager, tile, list_width, row_height * tile->n_items);
+        {
+          gtk_list_tile_set_area_size (self->item_manager,
+                                       tile,
+                                       list_width,
+                                       row_height * tile->n_items
+                                       + spacing * (tile->n_items - 1));
+        }
 
-      y += tile->area.height;
+      y += tile->area.height + spacing;
     }
 
   /* step 4: allocate the rest */