listbase: Compute rubberband region on-demand
Instead of storing the active items as we go, compute the affected items whenever the rubberband changes and in particular when the rubberband ends. That way, the rubberband is guaranteed to select a rectangle even after scrolling very far. This is achieved by having a get_items_in_rect() vfunc that selects all the items in the rubberbanded rectangle and returns them as a bitset.
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "gtkgridview.h"
|
||||
|
||||
#include "gtkbitset.h"
|
||||
#include "gtkintl.h"
|
||||
#include "gtklistbaseprivate.h"
|
||||
#include "gtklistitemfactory.h"
|
||||
@@ -452,6 +453,36 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GtkBitset *
|
||||
gtk_grid_view_get_items_in_rect (GtkListBase *base,
|
||||
const GdkRectangle *rect)
|
||||
{
|
||||
GtkGridView *self = GTK_GRID_VIEW (base);
|
||||
guint first_row, last_row, first_column, last_column, n_items;
|
||||
GtkBitset *result;
|
||||
|
||||
result = gtk_bitset_new_empty ();
|
||||
|
||||
n_items = gtk_list_base_get_n_items (base);
|
||||
if (n_items == 0)
|
||||
return result;
|
||||
|
||||
first_column = floor (rect->x / self->column_width);
|
||||
last_column = floor ((rect->x + rect->width) / self->column_width);
|
||||
if (!gtk_grid_view_get_cell_at_y (self, rect->y, &first_row, NULL, NULL))
|
||||
first_row = rect->y < 0 ? 0 : n_items - 1;
|
||||
if (!gtk_grid_view_get_cell_at_y (self, rect->y + rect->height, &last_row, NULL, NULL))
|
||||
last_row = rect->y < 0 ? 0 : n_items - 1;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static guint
|
||||
gtk_grid_view_move_focus_along (GtkListBase *base,
|
||||
guint pos,
|
||||
@@ -1000,6 +1031,7 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
|
||||
list_base_class->list_item_augment_func = cell_augment;
|
||||
list_base_class->get_allocation_along = gtk_grid_view_get_allocation_along;
|
||||
list_base_class->get_allocation_across = gtk_grid_view_get_allocation_across;
|
||||
list_base_class->get_items_in_rect = gtk_grid_view_get_items_in_rect;
|
||||
list_base_class->get_position_from_allocation = gtk_grid_view_get_position_from_allocation;
|
||||
list_base_class->move_focus_along = gtk_grid_view_move_focus_along;
|
||||
list_base_class->move_focus_across = gtk_grid_view_move_focus_across;
|
||||
|
||||
@@ -47,7 +47,6 @@ struct _RubberbandData
|
||||
double start_align_across; /* alignment in horizontal direction */
|
||||
double start_align_along; /* alignment in vertical direction */
|
||||
|
||||
GtkBitset *active;
|
||||
double pointer_x, pointer_y; /* mouse coordinates in widget space */
|
||||
gboolean modify;
|
||||
gboolean extend;
|
||||
@@ -1235,8 +1234,6 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
|
||||
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_backslash, GDK_CONTROL_MASK, "list.unselect-all", NULL);
|
||||
}
|
||||
|
||||
static void gtk_list_base_update_rubberband_selection (GtkListBase *self);
|
||||
|
||||
static gboolean
|
||||
autoscroll_cb (GtkWidget *widget,
|
||||
GdkFrameClock *frame_clock,
|
||||
@@ -1430,16 +1427,22 @@ gtk_list_base_widget_to_list (GtkListBase *self,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gtk_list_base_allocate_rubberband (GtkListBase *self)
|
||||
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);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_list_base_get_rubberband_coords (GtkListBase *self,
|
||||
GdkRectangle *rect)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
GtkRequisition min_size;
|
||||
int x1, x2, y1, y2;
|
||||
int offset_x, offset_y;
|
||||
|
||||
if (!priv->rubberband)
|
||||
return;
|
||||
return FALSE;
|
||||
|
||||
if (priv->rubberband->start_tracker == NULL)
|
||||
{
|
||||
@@ -1467,17 +1470,37 @@ gtk_list_base_allocate_rubberband (GtkListBase *self)
|
||||
priv->rubberband->pointer_x, priv->rubberband->pointer_y,
|
||||
&x2, &y2);
|
||||
|
||||
rect->x = MIN (x1, x2);
|
||||
rect->y = MIN (y1, y2);
|
||||
rect->width = ABS (x1 - x2) + 1;
|
||||
rect->height = ABS (y1 - y2) + 1;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_list_base_allocate_rubberband (GtkListBase *self)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
GtkRequisition min_size;
|
||||
GdkRectangle rect;
|
||||
int offset_x, offset_y;
|
||||
|
||||
if (!gtk_list_base_get_rubberband_coords (self, &rect))
|
||||
return;
|
||||
|
||||
gtk_widget_get_preferred_size (priv->rubberband->widget, &min_size, NULL);
|
||||
rect.width = MAX (min_size.width, rect.width);
|
||||
rect.height = MAX (min_size.height, rect.height);
|
||||
|
||||
gtk_list_base_get_adjustment_values (self, OPPOSITE_ORIENTATION (priv->orientation), &offset_x, NULL, NULL);
|
||||
gtk_list_base_get_adjustment_values (self, priv->orientation, &offset_y, NULL, NULL);
|
||||
rect.x -= offset_x;
|
||||
rect.y -= offset_y;
|
||||
|
||||
gtk_list_base_size_allocate_child (self,
|
||||
priv->rubberband->widget,
|
||||
MIN (x1, x2) - offset_x,
|
||||
MIN (y1, y2) - offset_y,
|
||||
MAX (min_size.width, ABS (x1 - x2) + 1),
|
||||
MAX (min_size.height, ABS (y1 - y2) + 1));
|
||||
rect.x, rect.y, rect.width, rect.height);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1518,7 +1541,6 @@ gtk_list_base_start_rubberband (GtkListBase *self,
|
||||
priv->rubberband->widget = gtk_gizmo_new ("rubberband",
|
||||
NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
gtk_widget_set_parent (priv->rubberband->widget, GTK_WIDGET (self));
|
||||
priv->rubberband->active = gtk_bitset_new_empty ();
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1543,10 +1565,17 @@ gtk_list_base_stop_rubberband (GtkListBase *self)
|
||||
if (model != NULL)
|
||||
{
|
||||
GtkBitset *selected, *mask;
|
||||
GdkRectangle rect;
|
||||
GtkBitset *rubberband_selection;
|
||||
|
||||
if (!gtk_list_base_get_rubberband_coords (self, &rect))
|
||||
return;
|
||||
|
||||
rubberband_selection = gtk_list_base_get_items_in_rect (self, &rect);
|
||||
|
||||
if (priv->rubberband->extend)
|
||||
{
|
||||
mask = gtk_bitset_ref (priv->rubberband->active);
|
||||
mask = gtk_bitset_ref (rubberband_selection);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1557,23 +1586,54 @@ gtk_list_base_stop_rubberband (GtkListBase *self)
|
||||
if (priv->rubberband->modify)
|
||||
selected = gtk_bitset_new_empty ();
|
||||
else
|
||||
selected = gtk_bitset_ref (priv->rubberband->active);
|
||||
selected = gtk_bitset_ref (rubberband_selection);
|
||||
|
||||
gtk_selection_model_set_selection (model, selected, mask);
|
||||
|
||||
gtk_bitset_unref (selected);
|
||||
gtk_bitset_unref (mask);
|
||||
gtk_bitset_unref (rubberband_selection);
|
||||
}
|
||||
|
||||
gtk_list_item_tracker_free (priv->item_manager, priv->rubberband->start_tracker);
|
||||
g_clear_pointer (&priv->rubberband->widget, gtk_widget_unparent);
|
||||
g_clear_pointer (&priv->rubberband->active, gtk_bitset_unref);
|
||||
g_free (priv->rubberband);
|
||||
priv->rubberband = NULL;
|
||||
|
||||
remove_autoscroll (self);
|
||||
}
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
static void
|
||||
gtk_list_base_update_rubberband_selection (GtkListBase *self)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
GtkListItemManagerItem *item;
|
||||
GdkRectangle rect;
|
||||
guint pos;
|
||||
GtkBitset *rubberband_selection;
|
||||
|
||||
if (!gtk_list_base_get_rubberband_coords (self, &rect))
|
||||
return;
|
||||
|
||||
rubberband_selection = gtk_list_base_get_items_in_rect (self, &rect);
|
||||
|
||||
pos = 0;
|
||||
for (item = gtk_list_item_manager_get_first (priv->item_manager);
|
||||
item != NULL;
|
||||
item = gtk_rb_tree_node_get_next (item))
|
||||
{
|
||||
if (item->widget)
|
||||
{
|
||||
if (gtk_bitset_contains (rubberband_selection, pos))
|
||||
gtk_widget_set_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE, FALSE);
|
||||
else
|
||||
gtk_widget_unset_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE);
|
||||
}
|
||||
|
||||
pos += item->n_items;
|
||||
}
|
||||
|
||||
gtk_bitset_unref (rubberband_selection);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1593,44 +1653,7 @@ gtk_list_base_update_rubberband (GtkListBase *self,
|
||||
|
||||
update_autoscroll (self, x, y);
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_list_base_update_rubberband_selection (GtkListBase *self)
|
||||
{
|
||||
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
|
||||
GdkRectangle rect;
|
||||
GdkRectangle alloc;
|
||||
GtkListItemManagerItem *item;
|
||||
|
||||
gtk_list_base_allocate_rubberband (self);
|
||||
gtk_widget_get_allocation (priv->rubberband->widget, &rect);
|
||||
|
||||
for (item = gtk_list_item_manager_get_first (priv->item_manager);
|
||||
item != NULL;
|
||||
item = gtk_rb_tree_node_get_next (item))
|
||||
{
|
||||
guint pos;
|
||||
|
||||
if (!item->widget)
|
||||
continue;
|
||||
|
||||
pos = gtk_list_item_manager_get_item_position (priv->item_manager, item);
|
||||
|
||||
gtk_widget_get_allocation (item->widget, &alloc);
|
||||
|
||||
if (gdk_rectangle_intersect (&rect, &alloc, &alloc))
|
||||
{
|
||||
gtk_bitset_add (priv->rubberband->active, pos);
|
||||
gtk_widget_set_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE, FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_bitset_remove (priv->rubberband->active, pos);
|
||||
gtk_widget_unset_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE);
|
||||
}
|
||||
}
|
||||
gtk_widget_queue_allocate (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -54,6 +54,8 @@ struct _GtkListBaseClass
|
||||
int along,
|
||||
guint *pos,
|
||||
cairo_rectangle_int_t *area);
|
||||
GtkBitset * (* get_items_in_rect) (GtkListBase *self,
|
||||
const cairo_rectangle_int_t *rect);
|
||||
guint (* move_focus_along) (GtkListBase *self,
|
||||
guint pos,
|
||||
int steps);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "gtklistview.h"
|
||||
|
||||
#include "gtkbitset.h"
|
||||
#include "gtkintl.h"
|
||||
#include "gtklistbaseprivate.h"
|
||||
#include "gtklistitemmanagerprivate.h"
|
||||
@@ -374,6 +375,36 @@ gtk_list_view_get_allocation_across (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;
|
||||
ListRow *row;
|
||||
|
||||
result = gtk_bitset_new_empty ();
|
||||
|
||||
n_items = gtk_list_base_get_n_items (base);
|
||||
if (n_items == 0)
|
||||
return result;
|
||||
|
||||
row = gtk_list_view_get_row_at_y (self, rect->y, NULL);
|
||||
if (row)
|
||||
first = gtk_list_item_manager_get_item_position (self->item_manager, row);
|
||||
else
|
||||
first = rect->y < 0 ? 0 : n_items - 1;
|
||||
row = gtk_list_view_get_row_at_y (self, rect->y + rect->height, NULL);
|
||||
if (row)
|
||||
last = gtk_list_item_manager_get_item_position (self->item_manager, row);
|
||||
else
|
||||
last = rect->y < 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,
|
||||
@@ -773,6 +804,7 @@ gtk_list_view_class_init (GtkListViewClass *klass)
|
||||
list_base_class->list_item_augment_func = list_row_augment;
|
||||
list_base_class->get_allocation_along = gtk_list_view_get_allocation_along;
|
||||
list_base_class->get_allocation_across = gtk_list_view_get_allocation_across;
|
||||
list_base_class->get_items_in_rect = gtk_list_view_get_items_in_rect;
|
||||
list_base_class->get_position_from_allocation = gtk_list_view_get_position_from_allocation;
|
||||
list_base_class->move_focus_along = gtk_list_view_move_focus_along;
|
||||
list_base_class->move_focus_across = gtk_list_view_move_focus_across;
|
||||
|
||||
Reference in New Issue
Block a user