From 334ca12d788bfbe53bfa16e1c74f7c78a2ad2032 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 9 Mar 2023 03:34:01 +0100 Subject: [PATCH 1/7] listview: Return an allocation for unallcoated items Just get the position right and give them a height of 0px, that should be good enough. If we don't do that, code will think the item doesn't exist, which is not what we want. --- gtk/gtkgridview.c | 35 ++++++++++++++++++++++++++++++++++- gtk/gtklistview.c | 46 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c index a0eb1acb06..6abbfc55da 100644 --- a/gtk/gtkgridview.c +++ b/gtk/gtkgridview.c @@ -237,9 +237,42 @@ gtk_grid_view_get_allocation (GtkListBase *base, guint offset; tile = gtk_list_item_manager_get_nth (self->item_manager, pos, &offset); - if (tile == NULL || tile->area.width <= 0 || tile->area.height <= 0) + if (tile == NULL) return FALSE; + if (tile->area.width <= 0 || tile->area.height <= 0) + { + /* item is not allocated yet */ + GtkListTile *other; + + for (other = gtk_rb_tree_node_get_previous (tile); + other; + other = gtk_rb_tree_node_get_previous (other)) + { + if (other->area.width || other->area.height) + { + area->x = other->area.x + other->area.width; + area->y = other->area.y + other->area.height; + break; + } + } + if (other == NULL) + { + for (other = gtk_rb_tree_node_get_next (tile); + other; + other = gtk_rb_tree_node_get_next (other)) + { + if (other->area.width || other->area.height) + { + area->x = other->area.x; + area->y = other->area.y; + break; + } + } + } + return TRUE; + } + *area = tile->area; if (tile->n_items > self->n_columns) diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 07f6e15303..6765165791 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -242,12 +242,48 @@ gtk_list_view_get_allocation (GtkListBase *base, return FALSE; *area = tile->area; - if (tile->n_items) - area->height /= tile->n_items; - if (offset) - area->y += offset * area->height; + if (area->width || area->height) + { + if (tile->n_items) + area->height /= tile->n_items; + if (offset) + area->y += offset * area->height; + } + else + { + /* item is not allocated yet */ + GtkListTile *other; - return area->width > 0 && area->height > 0; + for (other = gtk_rb_tree_node_get_previous (tile); + other; + other = gtk_rb_tree_node_get_previous (other)) + { + if (other->area.width || other->area.height) + { + area->x = other->area.x; + area->width = other->area.width; + area->y = other->area.y + other->area.height; + break; + } + } + if (other == NULL) + { + for (other = gtk_rb_tree_node_get_next (tile); + other; + other = gtk_rb_tree_node_get_next (other)) + { + if (other->area.width || other->area.height) + { + area->x = other->area.x; + area->width = other->area.width; + area->y = other->area.y; + break; + } + } + } + } + + return TRUE; } static GtkBitset * From 97e3c652518c66313c9e29dabed92e75b482fd1c Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 9 Mar 2023 04:02:22 +0100 Subject: [PATCH 2/7] listview: Move bounds check into base class This way, listview and gridview don't need to check if the rect is out of bounds and nothing is selected, a quick rectangle_intersect() does the job for them. --- gtk/gtkgridview.c | 14 ++++---------- gtk/gtklistbase.c | 9 ++++++++- gtk/gtklistview.c | 17 ----------------- 3 files changed, 12 insertions(+), 28 deletions(-) diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c index 6abbfc55da..dcfd1edd68 100644 --- a/gtk/gtkgridview.c +++ b/gtk/gtkgridview.c @@ -378,22 +378,16 @@ gtk_grid_view_get_items_in_rect (GtkListBase *base, { GtkGridView *self = GTK_GRID_VIEW (base); guint first_row, last_row, first_column, last_column; - GdkRectangle bounds; GtkBitset *result; result = gtk_bitset_new_empty (); - /* limit rect to the region that actually overlaps items */ - gtk_list_item_manager_get_tile_bounds (self->item_manager, &bounds); - if (!gdk_rectangle_intersect (&bounds, rect, &bounds)) - return result; - - first_column = fmax (floor (bounds.x / self->column_width), 0); - last_column = fmin (floor ((bounds.x + bounds.width) / self->column_width), self->n_columns - 1); + first_column = fmax (floor (rect->x / self->column_width), 0); + last_column = fmin (floor ((rect->x + rect->width) / self->column_width), self->n_columns - 1); /* match y = 0 here because we care about the rows, not the cells */ - if (!gtk_grid_view_get_position_from_allocation (base, 0, bounds.y, &first_row, NULL)) + if (!gtk_grid_view_get_position_from_allocation (base, 0, rect->y, &first_row, NULL)) g_return_val_if_reached (result); - if (!gtk_grid_view_get_position_from_allocation (base, 0, bounds.y + bounds.height - 1, &last_row, NULL)) + if (!gtk_grid_view_get_position_from_allocation (base, 0, rect->y + rect->height - 1, &last_row, NULL)) g_return_val_if_reached (result); gtk_bitset_add_rectangle (result, diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c index 1e4ee499a3..7b781bf3b3 100644 --- a/gtk/gtklistbase.c +++ b/gtk/gtklistbase.c @@ -1454,7 +1454,14 @@ static GtkBitset * gtk_list_base_get_items_in_rect (GtkListBase *self, const GdkRectangle *rect) { - return GTK_LIST_BASE_GET_CLASS (self)->get_items_in_rect (self, rect); + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + GdkRectangle bounds; + + gtk_list_item_manager_get_tile_bounds (priv->item_manager, &bounds); + if (!gdk_rectangle_intersect (&bounds, rect, &bounds)) + return gtk_bitset_new_empty (); + + return GTK_LIST_BASE_GET_CLASS (self)->get_items_in_rect (self, &bounds); } static gboolean diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 6765165791..f210ac26ce 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -186,20 +186,6 @@ dump (GtkListView *self) g_print (" => %u widgets in %u list rows\n", n_widgets, n_list_rows); } -static int -gtk_list_view_get_list_height (GtkListView *self) -{ - GtkListTile *tile; - GtkListTileAugment *aug; - - tile = gtk_list_item_manager_get_root (self->item_manager); - if (tile == NULL) - return 0; - - aug = gtk_list_tile_get_augment (self->item_manager, tile); - return aug->area.height; -} - static GtkListTile * gtk_list_view_split (GtkListBase *base, GtkListTile *tile, @@ -297,9 +283,6 @@ gtk_list_view_get_items_in_rect (GtkListBase *base, result = gtk_bitset_new_empty (); - if (rect->y >= gtk_list_view_get_list_height (self)) - return result; - n_items = gtk_list_base_get_n_items (base); if (n_items == 0) return result; From 5583fa60c179a8f7ec818829fb5bd53cd939bdcd Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 9 Mar 2023 04:42:45 +0100 Subject: [PATCH 3/7] listitemmanager: Add gtk_list_item_manager_get_nearest_tile() ... and make the tile finding code use distance. This also changes how gtk_list_item_manager_get_tile_at() finds the right tile, so this is a custom commit for bisectability. gtk_list_item_manager_get_nearest_tile() isn't used yet. --- gtk/gtklistitemmanager.c | 127 ++++++++++++++++++++++++++------ gtk/gtklistitemmanagerprivate.h | 4 + 2 files changed, 109 insertions(+), 22 deletions(-) diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index b34e6aa4a7..772b17d471 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -247,39 +247,94 @@ gtk_list_item_manager_get_nth (GtkListItemManager *self, return tile; } +/* This computes Manhattan distance */ +static int +rectangle_distance (const cairo_rectangle_int_t *rect, + int x, + int y) +{ + int x_dist, y_dist; + + if (rect->x > x) + x_dist = rect->x - x; + else if (rect->x + rect->width < x) + x_dist = x - (rect->x + rect->width); + else + x_dist = 0; + + if (rect->y > y) + y_dist = rect->y - y; + else if (rect->y + rect->height < y) + y_dist = y - (rect->y + rect->height); + else + y_dist = 0; + + return x_dist + y_dist; +} + static GtkListTile * gtk_list_tile_get_tile_at (GtkListItemManager *self, GtkListTile *tile, int x, - int y) + int y, + int *distance) { GtkListTileAugment *aug; - GtkListTile *subtile; + GtkListTile *left, *right, *result; + int dist, left_dist, right_dist; - aug = gtk_list_tile_get_augment (self, tile); - if (!gdk_rectangle_contains_point (&aug->area, x, y)) - return NULL; - - subtile = gtk_rb_tree_node_get_left (tile); - if (subtile) + left = gtk_rb_tree_node_get_left (tile); + if (left) { - subtile = gtk_list_tile_get_tile_at (self, subtile, x, y); - if (subtile) - return subtile; + aug = gtk_list_tile_get_augment (self, left); + left_dist = rectangle_distance (&aug->area, x, y); } - - if (gdk_rectangle_contains_point (&tile->area, x, y)) - return tile; - - subtile = gtk_rb_tree_node_get_right (tile); - if (subtile) + else + left_dist = *distance; + right = gtk_rb_tree_node_get_right (tile); + if (right) { - subtile = gtk_list_tile_get_tile_at (self, subtile, x, y); - if (subtile) - return subtile; + aug = gtk_list_tile_get_augment (self, right); + right_dist = rectangle_distance (&aug->area, x, y); } + else + right_dist = *distance; - return NULL; + dist = rectangle_distance (&tile->area, x, y); + result = NULL; + + while (TRUE) + { + if (dist < left_dist && dist < right_dist) + { + if (dist >= *distance) + return result; + + *distance = dist; + return tile; + } + + if (left_dist < right_dist) + { + if (left_dist >= *distance) + return result; + + left = gtk_list_tile_get_tile_at (self, left, x, y, distance); + if (left) + result = left; + left_dist = G_MAXINT; + } + else + { + if (right_dist >= *distance) + return result; + + right = gtk_list_tile_get_tile_at (self, right, x, y, distance); + if (right) + result = right; + right_dist = G_MAXINT; + } + } } /* @@ -299,7 +354,35 @@ gtk_list_item_manager_get_tile_at (GtkListItemManager *self, int x, int y) { - return gtk_list_tile_get_tile_at (self, gtk_list_item_manager_get_root (self), x, y); + int distance = 1; + + return gtk_list_tile_get_tile_at (self, gtk_list_item_manager_get_root (self), x, y, &distance); +} + +/* + * gtk_list_item_manager_get_nearest_tile: + * @self: a GtkListItemManager + * @x: x coordinate of tile + * @y: y coordinate of tile + * + * Finds the tile closest to the coordinates at (x, y). If no + * tile occupies the coordinates (for example, if the tile is out of bounds), + * Manhattan distance is used to find the nearest tile. + * + * If multiple tiles have the same distance, the one closest to the start + * will be returned. + * + * Returns: (nullable): The tile nearest to (x, y) or NULL if there are no + * tile + **/ +GtkListTile * +gtk_list_item_manager_get_nearest_tile (GtkListItemManager *self, + int x, + int y) +{ + int distance = G_MAXINT; + + return gtk_list_tile_get_tile_at (self, gtk_list_item_manager_get_root (self), x, y, &distance); } guint diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index eb10303618..7d4172aacc 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -78,6 +78,10 @@ gpointer gtk_list_item_manager_get_nth (GtkListItemMana GtkListTile * gtk_list_item_manager_get_tile_at (GtkListItemManager *self, int x, int y); +GtkListTile * gtk_list_item_manager_get_nearest_tile (GtkListItemManager *self, + int x, + int y); + guint gtk_list_tile_get_position (GtkListItemManager *self, GtkListTile *tile); From 196372f124138236bb638dc688e3f7b9d45a32e3 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 9 Mar 2023 04:43:51 +0100 Subject: [PATCH 4/7] listview: Support CSS border-spacing Hopefully the code can deal with gaps between the tiles. We use gtk_list_item_manager_get_nearest_tile() when necessary. --- gtk/gtklistbase.c | 26 ++++++++ gtk/gtklistbaseprivate.h | 3 + gtk/gtklistview.c | 133 +++++++++++++++++++++++---------------- 3 files changed, 107 insertions(+), 55 deletions(-) diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c index 7b781bf3b3..81ac56cc6f 100644 --- a/gtk/gtklistbase.c +++ b/gtk/gtklistbase.c @@ -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) { diff --git a/gtk/gtklistbaseprivate.h b/gtk/gtklistbaseprivate.h index c79bd8b13d..e8ab5b5db7 100644 --- a/gtk/gtklistbaseprivate.h +++ b/gtk/gtklistbaseprivate.h @@ -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); diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index f210ac26ce..f822c53529 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -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 */ From 670cf6201d6c4c0d54c4c12390a219598ef2ace4 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 9 Mar 2023 05:30:25 +0100 Subject: [PATCH 5/7] gridview: Use gtk_list_item_manager_get_nearest_tile() Simplifies gtk_grid_view_get_position_from_allocation() a bit because we can omit the bounds shenanigan. --- gtk/gtkgridview.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c index dcfd1edd68..6b245d8dfe 100644 --- a/gtk/gtkgridview.c +++ b/gtk/gtkgridview.c @@ -302,15 +302,8 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base, GtkGridView *self = GTK_GRID_VIEW (base); GtkListTile *tile; guint pos; - GdkRectangle bounds; - gtk_list_item_manager_get_tile_bounds (self->item_manager, &bounds); - if (bounds.width <= 0 || bounds.height <= 0) - return FALSE; - x = CLAMP (x, bounds.x, bounds.x + bounds.width - 1); - y = CLAMP (y, bounds.y, bounds.y + bounds.height - 1); - - 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; @@ -329,7 +322,7 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base, if (tile->n_items > 1) { /* offset in x direction */ - pos += (x - tile->area.x) / self->column_width; + pos += MAX (tile->area.width - 1, x - tile->area.x) / self->column_width; if (area) { guint col = MIN (x / self->column_width, self->n_columns - 1); @@ -342,7 +335,7 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base, { guint rows_in_tile = tile->n_items / self->n_columns; guint row_height = tile->area.height / rows_in_tile; - guint row_index = (y - tile->area.y) / row_height; + guint row_index = MAX (tile->area.height - 1, y - tile->area.y) / row_height; pos += self->n_columns * row_index; if (area) From 4b73be18c7fea012735921a2dd784caf22b3c664 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 9 Mar 2023 17:22:06 +0100 Subject: [PATCH 6/7] gridview: Introduce column size functions This is in preparation for border-spacing integration. --- gtk/gtkgridview.c | 60 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c index 6b245d8dfe..5fc446cf8e 100644 --- a/gtk/gtkgridview.c +++ b/gtk/gtkgridview.c @@ -151,6 +151,27 @@ dump (GtkGridView *self) g_print (" => %u widgets in %u list rows\n", n_widgets, n_list_rows); } +static int +column_index (GtkGridView *self, + int x) +{ + return x / self->column_width; +} + +static int +column_start (GtkGridView *self, + int col) +{ + return ceil (self->column_width * col); +} + +static int +column_end (GtkGridView *self, + int col) +{ + return ceil (self->column_width * (col + 1)); +} + static GtkListTile * gtk_grid_view_split (GtkListBase *base, GtkListTile *tile, @@ -209,19 +230,20 @@ gtk_grid_view_split (GtkListBase *base, g_assert (tile->n_items <= self->n_columns); /* now it's a single row, do a split at the column boundary */ - col = tile->area.x / self->column_width; + col = column_index (self, tile->area.x); split = gtk_list_tile_split (self->item_manager, tile, n_items); gtk_list_tile_set_area (self->item_manager, split, &(GdkRectangle) { - ceil ((col + n_items) * self->column_width), + column_start (self, col + n_items), tile->area.y, - ceil ((col + n_items + split->n_items) * self->column_width), + column_end (self, col + n_items + split->n_items - 1) + - column_start (self, col + n_items), tile->area.height, }); gtk_list_tile_set_area_size (self->item_manager, tile, - ceil ((col + n_items) * self->column_width) - tile->area.x, + column_end (self, col + n_items - 1) - tile->area.x, tile->area.height); return split; @@ -284,9 +306,9 @@ gtk_grid_view_get_allocation (GtkListBase *base, if (tile->n_items > 1) { - guint col = area->x / self->column_width; - area->x = ceil ((col + offset) * self->column_width); - area->width = ceil ((col + offset + 1) * self->column_width) - area->x; + guint col = column_index (self, area->x); + area->x = column_start (self, col + offset); + area->width = column_end (self, col + offset) - area->x; } return TRUE; @@ -322,12 +344,12 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base, if (tile->n_items > 1) { /* offset in x direction */ - pos += MAX (tile->area.width - 1, x - tile->area.x) / self->column_width; + pos += column_index (self, MAX (tile->area.width - 1, x - tile->area.x)); if (area) { - guint col = MIN (x / self->column_width, self->n_columns - 1); - area->x = ceil (col * self->column_width); - area->width = ceil ((col + 1) * self->column_width) - area->x; + guint col = MIN (column_index (self, x), self->n_columns - 1); + area->x = column_start (self, col); + area->width = column_end (self, col) - area->x; } /* offset in y direction */ @@ -375,8 +397,8 @@ gtk_grid_view_get_items_in_rect (GtkListBase *base, result = gtk_bitset_new_empty (); - first_column = fmax (floor (rect->x / self->column_width), 0); - last_column = fmin (floor ((rect->x + rect->width) / self->column_width), self->n_columns - 1); + first_column = MAX (column_index (self, rect->x), 0); + last_column = MIN (column_index (self, rect->x + rect->width), self->n_columns - 1); /* match y = 0 here because we care about the rows, not the cells */ if (!gtk_grid_view_get_position_from_allocation (base, 0, rect->y, &first_row, NULL)) g_return_val_if_reached (result); @@ -703,7 +725,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget, { gtk_list_tile_set_area_size (self->item_manager, start, - ceil (self->column_width * (i + start->n_items)) - ceil (self->column_width * i), + column_end (self, i + start->n_items - 1) - column_start (self, i), row_height); i += start->n_items; } @@ -724,7 +746,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget, { gtk_list_tile_set_area_position (self->item_manager, tile, - ceil (self->column_width * i), + column_start (self, i), y); if (tile->n_items >= self->n_columns && tile->widget == NULL) { @@ -732,7 +754,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget, g_assert (tile->n_items % self->n_columns == 0); gtk_list_tile_set_area_size (self->item_manager, tile, - ceil (self->column_width * self->n_columns), + column_end (self, self->n_columns - 1) - column_start (self, 0), unknown_row_height * (tile->n_items / self->n_columns)); y += tile->area.height; } @@ -744,7 +766,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget, * be a multirow tile but it may have no widgets either */ gtk_list_tile_set_area_size (self->item_manager, tile, - ceil (self->column_width * (i + tile->n_items)) - ceil (self->column_width * i), + column_end (self, i + tile->n_items - 1) - column_start (self, i), unknown_row_height); } i += tile->n_items; @@ -765,11 +787,11 @@ gtk_grid_view_size_allocate (GtkWidget *widget, filler = gtk_list_tile_split (self->item_manager, tile, tile->n_items); gtk_list_tile_set_area_position (self->item_manager, filler, - ceil (self->column_width * i), + column_start (self, i), y); gtk_list_tile_set_area_size (self->item_manager, filler, - ceil (self->column_width * self->n_columns) - filler->area.x, + column_end (self, self->n_columns - 1) - filler->area.x, tile->area.height); } From 6a874e6887f5717f9adf5663d5068f52250c8778 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 10 Mar 2023 05:16:25 +0100 Subject: [PATCH 7/7] gridview: Add border-spacing support omg, this is complicated code. --- gtk/gtkgridview.c | 146 +++++++++++++++++++++++++++++----------------- 1 file changed, 93 insertions(+), 53 deletions(-) diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c index 5fc446cf8e..41ea645125 100644 --- a/gtk/gtkgridview.c +++ b/gtk/gtkgridview.c @@ -153,23 +153,26 @@ dump (GtkGridView *self) static int column_index (GtkGridView *self, + int spacing, int x) { - return x / self->column_width; + return (x + spacing / 2.0) / (self->column_width + spacing); } static int column_start (GtkGridView *self, + int spacing, int col) { - return ceil (self->column_width * col); + return ceil ((self->column_width + spacing) * col); } static int column_end (GtkGridView *self, + int spacing, int col) { - return ceil (self->column_width * (col + 1)); + return ceil (self->column_width * (col + 1) + (spacing * col)); } static GtkListTile * @@ -180,8 +183,11 @@ gtk_grid_view_split (GtkListBase *base, GtkGridView *self = GTK_GRID_VIEW (base); GtkListTile *split; guint col, row_height; + int xspacing, yspacing; - row_height = tile->area.height / MAX (tile->n_items / self->n_columns, 1); + gtk_list_base_get_border_spacing (base, &xspacing, &yspacing); + + row_height = (tile->area.height + yspacing) / MAX (tile->n_items / self->n_columns, 1) - yspacing; /* split off the multirow at the top */ if (n_items >= self->n_columns) @@ -195,14 +201,14 @@ gtk_grid_view_split (GtkListBase *base, tile, &(GdkRectangle) { split->area.x, - split->area.y + row_height * top_rows, + split->area.y + (row_height + yspacing) * top_rows, split->area.width, - split->area.height - row_height * top_rows, + split->area.height - (row_height + yspacing) * top_rows, }); gtk_list_tile_set_area_size (self->item_manager, split, split->area.width, - row_height * top_rows); + row_height * top_rows + yspacing * (top_rows - 1)); n_items -= top_items; if (n_items == 0) return tile; @@ -216,9 +222,9 @@ gtk_grid_view_split (GtkListBase *base, split, &(GdkRectangle) { tile->area.x, - tile->area.y + row_height, + tile->area.y + row_height + yspacing, tile->area.width, - tile->area.height - row_height, + tile->area.height - row_height - yspacing, }); gtk_list_tile_set_area_size (self->item_manager, tile, @@ -230,20 +236,20 @@ gtk_grid_view_split (GtkListBase *base, g_assert (tile->n_items <= self->n_columns); /* now it's a single row, do a split at the column boundary */ - col = column_index (self, tile->area.x); + col = column_index (self, xspacing, tile->area.x); split = gtk_list_tile_split (self->item_manager, tile, n_items); gtk_list_tile_set_area (self->item_manager, split, &(GdkRectangle) { - column_start (self, col + n_items), + column_start (self, xspacing, col + n_items), tile->area.y, - column_end (self, col + n_items + split->n_items - 1) - - column_start (self, col + n_items), + column_end (self, xspacing, col + n_items + split->n_items - 1) + - column_start (self, xspacing, col + n_items), tile->area.height, }); gtk_list_tile_set_area_size (self->item_manager, tile, - column_end (self, col + n_items - 1) - tile->area.x, + column_end (self, xspacing, col + n_items - 1) - tile->area.x, tile->area.height); return split; @@ -257,11 +263,14 @@ gtk_grid_view_get_allocation (GtkListBase *base, GtkGridView *self = GTK_GRID_VIEW (base); GtkListTile *tile; guint offset; + int xspacing, yspacing; tile = gtk_list_item_manager_get_nth (self->item_manager, pos, &offset); if (tile == NULL) return FALSE; + gtk_list_base_get_border_spacing (base, &xspacing, &yspacing); + if (tile->area.width <= 0 || tile->area.height <= 0) { /* item is not allocated yet */ @@ -299,16 +308,16 @@ gtk_grid_view_get_allocation (GtkListBase *base, if (tile->n_items > self->n_columns) { - area->height /= (tile->n_items / self->n_columns); - area->y += (offset / self->n_columns) * area->height; + area->height = (area->height + yspacing) / (tile->n_items / self->n_columns) - yspacing; + area->y += (offset / self->n_columns) * (area->height + yspacing); offset %= self->n_columns; } if (tile->n_items > 1) { - guint col = column_index (self, area->x); - area->x = column_start (self, col + offset); - area->width = column_end (self, col + offset) - area->x; + guint col = column_index (self, xspacing, area->x); + area->x = column_start (self, xspacing, col + offset); + area->width = column_end (self, xspacing, col + offset) - area->x; } return TRUE; @@ -343,26 +352,30 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base, pos = gtk_list_tile_get_position (self->item_manager, tile); if (tile->n_items > 1) { + int xspacing, yspacing; + + gtk_list_base_get_border_spacing (base, &xspacing, &yspacing); + /* offset in x direction */ - pos += column_index (self, MAX (tile->area.width - 1, x - tile->area.x)); + pos += column_index (self, xspacing, MAX (tile->area.width - 1, x - tile->area.x)); if (area) { - guint col = MIN (column_index (self, x), self->n_columns - 1); - area->x = column_start (self, col); - area->width = column_end (self, col) - area->x; + guint col = MIN (column_index (self, xspacing, x), self->n_columns - 1); + area->x = column_start (self, xspacing, col); + area->width = column_end (self, xspacing, col) - area->x; } /* offset in y direction */ if (tile->n_items > self->n_columns) { guint rows_in_tile = tile->n_items / self->n_columns; - guint row_height = tile->area.height / rows_in_tile; - guint row_index = MAX (tile->area.height - 1, y - tile->area.y) / row_height; + guint row_height = (tile->area.height + yspacing) / rows_in_tile - yspacing; + guint row_index = MIN (tile->area.height - 1, y - tile->area.y) / (row_height + yspacing); pos += self->n_columns * row_index; if (area) { - area->y = tile->area.y + row_index * row_height; + area->y = tile->area.y + row_index * (row_height + yspacing); area->height = row_height; } } @@ -393,23 +406,37 @@ gtk_grid_view_get_items_in_rect (GtkListBase *base, { GtkGridView *self = GTK_GRID_VIEW (base); guint first_row, last_row, first_column, last_column; + cairo_rectangle_int_t area; + int xspacing, yspacing; GtkBitset *result; + gtk_list_base_get_border_spacing (base, &xspacing, &yspacing); result = gtk_bitset_new_empty (); - first_column = MAX (column_index (self, rect->x), 0); - last_column = MIN (column_index (self, rect->x + rect->width), self->n_columns - 1); + first_column = MAX (column_index (self, xspacing, rect->x), 0); + if (column_end (self, xspacing, first_column) <= rect->x) + first_column++; + last_column = MIN (column_index (self, xspacing, rect->x + rect->width), self->n_columns - 1); + if (column_start (self, xspacing, last_column) > rect->x + rect->width) + last_column--; /* match y = 0 here because we care about the rows, not the cells */ - if (!gtk_grid_view_get_position_from_allocation (base, 0, rect->y, &first_row, NULL)) + if (!gtk_grid_view_get_position_from_allocation (base, column_start (self, xspacing, 0), rect->y, &first_row, &area)) g_return_val_if_reached (result); - if (!gtk_grid_view_get_position_from_allocation (base, 0, rect->y + rect->height - 1, &last_row, NULL)) + if (area.y + area.height < rect->y) + first_row += self->n_columns; + if (!gtk_grid_view_get_position_from_allocation (base, column_start (self, xspacing, 0), rect->y + rect->height, &last_row, NULL)) g_return_val_if_reached (result); + if (area.y >= rect->y + rect->height) + last_row -= self->n_columns; - gtk_bitset_add_rectangle (result, - first_row + first_column, - last_column - first_column + 1, - (last_row - first_row) / self->n_columns + 1, - self->n_columns); + if (first_column <= last_column && first_row <= last_row) + { + gtk_bitset_add_rectangle (result, + first_row + first_column, + last_column - first_column + 1, + (last_row - first_row) / self->n_columns + 1, + self->n_columns); + } return result; } @@ -511,16 +538,20 @@ gtk_grid_view_measure_across (GtkWidget *widget, int *natural) { GtkGridView *self = GTK_GRID_VIEW (widget); + int xspacing; + + gtk_list_base_get_border_spacing (GTK_LIST_BASE (widget), &xspacing, NULL); gtk_grid_view_measure_column_size (self, minimum, natural); - *minimum *= self->min_columns; - *natural *= self->max_columns; + *minimum = (*minimum + xspacing) * self->min_columns - xspacing; + *natural = (*natural + xspacing) * self->max_columns - xspacing; } static guint gtk_grid_view_compute_n_columns (GtkGridView *self, guint for_size, + int border_spacing, int min, int nat) { @@ -529,9 +560,9 @@ gtk_grid_view_compute_n_columns (GtkGridView *self, /* rounding down is exactly what we want here, so int division works */ if (gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), gtk_list_base_get_opposite_orientation (GTK_LIST_BASE (self))) == GTK_SCROLL_MINIMUM) - n_columns = for_size / MAX (1, min); + n_columns = (for_size + border_spacing) / MAX (1, min + border_spacing); else - n_columns = for_size / MAX (1, nat); + n_columns = (for_size + border_spacing) / MAX (1, nat + border_spacing); n_columns = CLAMP (n_columns, self->min_columns, self->max_columns); @@ -550,11 +581,13 @@ gtk_grid_view_measure_list (GtkWidget *widget, GtkScrollablePolicy scroll_policy; GtkListTile *tile; int height, row_height, child_min, child_nat, column_size, col_min, col_nat; + int xspacing, yspacing; gboolean measured; GArray *heights; guint n_unknown, n_columns; guint i; + gtk_list_base_get_border_spacing (GTK_LIST_BASE (self), &xspacing, &yspacing); scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), gtk_list_base_get_orientation (GTK_LIST_BASE (self))); heights = g_array_new (FALSE, FALSE, sizeof (int)); n_unknown = 0; @@ -562,7 +595,7 @@ gtk_grid_view_measure_list (GtkWidget *widget, 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); + n_columns = gtk_grid_view_compute_n_columns (self, for_size, xspacing, col_min, col_nat); column_size = for_size / n_columns; i = 0; @@ -593,7 +626,7 @@ gtk_grid_view_measure_list (GtkWidget *widget, { g_array_append_val (heights, row_height); i -= n_columns; - height += row_height; + height += row_height + yspacing; measured = FALSE; row_height = 0; } @@ -607,14 +640,17 @@ gtk_grid_view_measure_list (GtkWidget *widget, if (measured) { g_array_append_val (heights, row_height); - height += row_height; + height += row_height + yspacing; } else n_unknown++; } if (n_unknown) - height += n_unknown * gtk_grid_view_get_unknown_row_size (self, heights); + height += n_unknown * (gtk_grid_view_get_unknown_row_size (self, heights) + yspacing); + /* if we have a height, we have at least one row, and because we added spacing for every row... */ + if (height) + height -= yspacing; g_array_free (heights, TRUE); @@ -651,12 +687,13 @@ gtk_grid_view_size_allocate (GtkWidget *widget, int min_row_height, unknown_row_height, row_height, col_min, col_nat; GtkOrientation orientation; GtkScrollablePolicy scroll_policy; - int y; + int y, xspacing, yspacing; guint i; orientation = gtk_list_base_get_orientation (GTK_LIST_BASE (self)); scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), orientation); min_row_height = ceil ((double) height / GTK_GRID_VIEW_MAX_VISIBLE_ROWS); + gtk_list_base_get_border_spacing (GTK_LIST_BASE (self), &xspacing, &yspacing); /* 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)); @@ -670,6 +707,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget, gtk_grid_view_measure_column_size (self, &col_min, &col_nat); self->n_columns = gtk_grid_view_compute_n_columns (self, orientation == GTK_ORIENTATION_VERTICAL ? width : height, + xspacing, col_min, col_nat); self->column_width = (orientation == GTK_ORIENTATION_VERTICAL ? width : height) / self->n_columns; self->column_width = MAX (self->column_width, col_min); @@ -725,7 +763,8 @@ gtk_grid_view_size_allocate (GtkWidget *widget, { gtk_list_tile_set_area_size (self->item_manager, start, - column_end (self, i + start->n_items - 1) - column_start (self, i), + column_end (self, xspacing, i + start->n_items - 1) + - column_start (self, xspacing, i), row_height); i += start->n_items; } @@ -746,7 +785,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget, { gtk_list_tile_set_area_position (self->item_manager, tile, - column_start (self, i), + column_start (self, xspacing, i), y); if (tile->n_items >= self->n_columns && tile->widget == NULL) { @@ -754,9 +793,10 @@ gtk_grid_view_size_allocate (GtkWidget *widget, g_assert (tile->n_items % self->n_columns == 0); gtk_list_tile_set_area_size (self->item_manager, tile, - column_end (self, self->n_columns - 1) - column_start (self, 0), - unknown_row_height * (tile->n_items / self->n_columns)); - y += tile->area.height; + column_end (self, xspacing, self->n_columns - 1) + - column_start (self, xspacing, 0), + (unknown_row_height + yspacing) * (tile->n_items / self->n_columns) - yspacing); + y += tile->area.height + yspacing; } else { @@ -766,7 +806,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget, * be a multirow tile but it may have no widgets either */ gtk_list_tile_set_area_size (self->item_manager, tile, - column_end (self, i + tile->n_items - 1) - column_start (self, i), + column_end (self, xspacing, i + tile->n_items - 1) - tile->area.x, unknown_row_height); } i += tile->n_items; @@ -775,7 +815,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget, if (i >= self->n_columns) { g_assert (i == self->n_columns); - y += tile->area.height; + y += tile->area.height + yspacing; i = 0; } } @@ -787,11 +827,11 @@ gtk_grid_view_size_allocate (GtkWidget *widget, filler = gtk_list_tile_split (self->item_manager, tile, tile->n_items); gtk_list_tile_set_area_position (self->item_manager, filler, - column_start (self, i), + column_start (self, xspacing, i), y); gtk_list_tile_set_area_size (self->item_manager, filler, - column_end (self, self->n_columns - 1) - filler->area.x, + column_end (self, xspacing, self->n_columns - 1) - filler->area.x, tile->area.height); }