From 105be5a457bb29a4e3316aa502e9764830e1e687 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 7 Mar 2023 05:29:31 +0100 Subject: [PATCH 1/8] gtk: Add GtkScrollInfo This struct carries information about scrolling a scrollable, so that individual scrollables can share this struct for their scrolling APIs. For now, there's not much information here, we're still trying to cook up an API that works well. --- gtk/gtk.h | 1 + gtk/gtkscrollinfo.c | 249 +++++++++++++++++++++++++++++++++++++ gtk/gtkscrollinfo.h | 61 +++++++++ gtk/gtkscrollinfoprivate.h | 42 +++++++ gtk/gtktypes.h | 1 + gtk/meson.build | 2 + 6 files changed, 356 insertions(+) create mode 100644 gtk/gtkscrollinfo.c create mode 100644 gtk/gtkscrollinfo.h create mode 100644 gtk/gtkscrollinfoprivate.h diff --git a/gtk/gtk.h b/gtk/gtk.h index c5f9b4b583..6a849ecf6c 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -223,6 +223,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkscrollinfo.c b/gtk/gtkscrollinfo.c new file mode 100644 index 0000000000..513eccacc9 --- /dev/null +++ b/gtk/gtkscrollinfo.c @@ -0,0 +1,249 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2023 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +/** + * GtkScrollInfo: + * + * The `GtkScrollInfo` can be used to provide more accurate data on how a scroll + * operation should be performed. + * + * Scrolling functions usually allow passing a %NULL scroll info which will cause + * the default values to be used and just scroll the element into view. + * + * Since: 4.12 + */ + +#include "config.h" + +#include "gtkscrollinfoprivate.h" + +#include + +struct _GtkScrollInfo +{ + guint ref_count; + + gboolean enabled[2]; /* directions */ +}; + +static GtkScrollInfo default_scroll_info = { + 1, + { TRUE, TRUE } +}; + +G_DEFINE_BOXED_TYPE (GtkScrollInfo, gtk_scroll_info, + gtk_scroll_info_ref, + gtk_scroll_info_unref) + + +/** + * gtk_scroll_info_new: + * + * Creates a new scroll info for scrolling an element into view. + * + * Returns: A new scroll info + * + * Since: 4.12 + **/ +GtkScrollInfo * +gtk_scroll_info_new (void) +{ + GtkScrollInfo *self; + + self = g_new0 (GtkScrollInfo, 1); + self->ref_count = 1; + self->enabled[GTK_ORIENTATION_HORIZONTAL] = TRUE; + self->enabled[GTK_ORIENTATION_VERTICAL] = TRUE; + + return self; +} + +/** + * gtk_scroll_info_ref: + * @self: a `GtkScrollInfo` + * + * Increases the reference count of a `GtkScrollInfo` by one. + * + * Returns: the passed in `GtkScrollInfo`. + * + * Since: 4.12 + */ +GtkScrollInfo * +gtk_scroll_info_ref (GtkScrollInfo *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + self->ref_count++; + + return self; +} + +/** + * gtk_scroll_info_unref: + * @self: a `GtkScrollInfo` + * + * Decreases the reference count of a `GtkScrollInfo` by one. + * + * If the resulting reference count is zero, frees the self. + * + * Since: 4.12 + */ +void +gtk_scroll_info_unref (GtkScrollInfo *self) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (self->ref_count > 0); + + self->ref_count--; + if (self->ref_count > 0) + return; + + g_free (self); +} + +/** + * gtk_scroll_info_set_enable_horizontal: + * @self: a `GtkScrollInfo` + * @horizontal: if scrolling in the horizontal direction + * should happen + * + * Turns horizontal scrolling on or off. + * + * Since: 4.12 + **/ +void +gtk_scroll_info_set_enable_horizontal (GtkScrollInfo *self, + gboolean horizontal) +{ + g_return_if_fail (self != NULL); + + self->enabled[GTK_ORIENTATION_HORIZONTAL] = horizontal; +} + +/** + * gtk_scroll_info_get_enable_horizontal: + * @self: a `GtkScrollInfo` + * + * Checks if horizontal scrolling is enabled. + * + * Returns: %TRUE if horizontal scrolling is enabled. + * + * Since: 4.12 + **/ +gboolean +gtk_scroll_info_get_enable_horizontal (GtkScrollInfo *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + + return self->enabled[GTK_ORIENTATION_HORIZONTAL]; +} + +/** + * gtk_scroll_info_set_enable_vertical: + * @self: a `GtkScrollInfo` + * @vertical: if scrolling in the vertical direction + * should happen + * + * Turns vertical scrolling on or off. + * + * Since: 4.12 + **/ +void +gtk_scroll_info_set_enable_vertical (GtkScrollInfo *self, + gboolean vertical) +{ + g_return_if_fail (self != NULL); + + self->enabled[GTK_ORIENTATION_VERTICAL] = vertical; +} + +/** + * gtk_scroll_info_get_enable_vertical: + * @self: a `GtkScrollInfo` + * + * Checks if vertical scrolling is enabled. + * + * Returns: %TRUE if vertical scrolling is enabled. + * + * Since: 4.12 + **/ +gboolean +gtk_scroll_info_get_enable_vertical (GtkScrollInfo *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + + return self->enabled[GTK_ORIENTATION_VERTICAL]; +} + +int +gtk_scroll_info_compute_for_orientation (GtkScrollInfo *self, + GtkOrientation orientation, + int area_origin, + int area_size, + int viewport_origin, + int viewport_size) +{ + float origin, size; + int delta; + + if (self == NULL) + self = &default_scroll_info; + + if (!self->enabled[orientation]) + return viewport_origin; + + origin = viewport_origin; + size = viewport_size; + + if (area_origin <= origin) + delta = area_origin - ceil (origin); + else if (area_origin + area_size > origin + size) + delta = area_origin + area_size - floor (origin + size); + else + delta = 0; + + return viewport_origin + delta; +} + +/* + * gtk_scroll_info_compute_scroll: + * @self: a `GtkScrollInfo` + * @area: area to scroll + * @viewport: viewport area to scroll into + * @out_x: (out): x coordinate to scroll viewport to + * @out_y: (out): y coordinate to scroll viewport to + * + * Computes The new x/y coordinate to move the viewport to + * according to this scroll info. + **/ +void +gtk_scroll_info_compute_scroll (GtkScrollInfo *self, + const cairo_rectangle_int_t *area, + const cairo_rectangle_int_t *viewport, + int *out_x, + int *out_y) +{ + *out_x = gtk_scroll_info_compute_for_orientation (self, + GTK_ORIENTATION_HORIZONTAL, + area->x, area->width, + viewport->x, viewport->width); + *out_y = gtk_scroll_info_compute_for_orientation (self, + GTK_ORIENTATION_VERTICAL, + area->y, area->height, + viewport->y, viewport->height); +} + diff --git a/gtk/gtkscrollinfo.h b/gtk/gtkscrollinfo.h new file mode 100644 index 0000000000..56cbf63d21 --- /dev/null +++ b/gtk/gtkscrollinfo.h @@ -0,0 +1,61 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2023 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + + +#include +#include +#include + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_SCROLL_INFO (gtk_scroll_info_get_type ()) + +GDK_AVAILABLE_IN_4_12 +GType gtk_scroll_info_get_type (void) G_GNUC_CONST; +GDK_AVAILABLE_IN_4_12 +GtkScrollInfo * gtk_scroll_info_new (void); + +GDK_AVAILABLE_IN_4_12 +GtkScrollInfo * gtk_scroll_info_ref (GtkScrollInfo *self); +GDK_AVAILABLE_IN_4_12 +void gtk_scroll_info_unref (GtkScrollInfo *self); + +GDK_AVAILABLE_IN_4_12 +void gtk_scroll_info_set_enable_horizontal (GtkScrollInfo *self, + gboolean horizontal); +GDK_AVAILABLE_IN_4_12 +gboolean gtk_scroll_info_get_enable_horizontal (GtkScrollInfo *self); + +GDK_AVAILABLE_IN_4_12 +void gtk_scroll_info_set_enable_vertical (GtkScrollInfo *self, + gboolean vertical); +GDK_AVAILABLE_IN_4_12 +gboolean gtk_scroll_info_get_enable_vertical (GtkScrollInfo *self); + + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkScrollInfo, gtk_scroll_info_unref) + +G_END_DECLS + diff --git a/gtk/gtkscrollinfoprivate.h b/gtk/gtkscrollinfoprivate.h new file mode 100644 index 0000000000..f1ac1a6b59 --- /dev/null +++ b/gtk/gtkscrollinfoprivate.h @@ -0,0 +1,42 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2023 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + + +#include + +G_BEGIN_DECLS + +void gtk_scroll_info_compute_scroll (GtkScrollInfo *self, + const cairo_rectangle_int_t *area, + const cairo_rectangle_int_t *viewport, + int *out_x, + int *out_y); +int gtk_scroll_info_compute_for_orientation (GtkScrollInfo *self, + GtkOrientation orientation, + int area_origin, + int area_size, + int viewport_origin, + int viewport_size); + +G_END_DECLS + diff --git a/gtk/gtktypes.h b/gtk/gtktypes.h index 6efa78f837..e30194a1e5 100644 --- a/gtk/gtktypes.h +++ b/gtk/gtktypes.h @@ -46,6 +46,7 @@ typedef struct _GtkListItemFactory GtkListItemFactory; typedef struct _GtkNative GtkNative; typedef struct _GtkRequisition GtkRequisition; typedef struct _GtkRoot GtkRoot; +typedef struct _GtkScrollInfo GtkScrollInfo; typedef struct _GtkSettings GtkSettings; typedef struct _GtkShortcut GtkShortcut; typedef struct _GtkShortcutAction GtkShortcutAction; diff --git a/gtk/meson.build b/gtk/meson.build index 19bc221277..765925dc26 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -327,6 +327,7 @@ gtk_public_sources = files([ 'gtkscalebutton.c', 'gtkscrollable.c', 'gtkscrollbar.c', + 'gtkscrollinfo.c', 'gtkscrolledwindow.c', 'gtksearchbar.c', 'gtksearchentry.c', @@ -557,6 +558,7 @@ gtk_public_headers = files([ 'gtkscalebutton.h', 'gtkscrollable.h', 'gtkscrollbar.h', + 'gtkscrollinfo.h', 'gtkscrolledwindow.h', 'gtksearchbar.h', 'gtksearchentry.h', From 2a5f1dc6c75d17b5b3da11aa96d969bd66e9288f Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 7 Mar 2023 05:33:47 +0100 Subject: [PATCH 2/8] viewport: Add gtk_viewport_scroll_to() First implementation of GtkScrollInfo --- gtk/gtkviewport.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++ gtk/gtkviewport.h | 5 +++++ 2 files changed, 58 insertions(+) diff --git a/gtk/gtkviewport.c b/gtk/gtkviewport.c index 5f9cc86fe4..8fc93718da 100644 --- a/gtk/gtkviewport.c +++ b/gtk/gtkviewport.c @@ -30,11 +30,13 @@ #include "gtkmarshalers.h" #include "gtkprivate.h" #include "gtkscrollable.h" +#include "gtkscrollinfoprivate.h" #include "gtktypebuiltins.h" #include "gtkwidgetprivate.h" #include "gtkbuildable.h" #include "gtktext.h" +#include /** * GtkViewport: @@ -720,3 +722,54 @@ gtk_viewport_get_child (GtkViewport *viewport) return viewport->child; } +/** + * gtk_viewport_scroll_to: + * @viewport: a `GtkViewport` + * @descendant: a descendant widget of the viewport + * @scroll: (nullable) (transfer full): details of how to perform + * the scroll operation or NULL to scroll into view + * + * Scrolls a descendant of the viewport into view. + * + * The viewport and the descendant must be visible and mapped for + * this function to work, otherwise no scrolling will be performed. + * + * Since: 4.12 + **/ +void +gtk_viewport_scroll_to (GtkViewport *viewport, + GtkWidget *descendant, + GtkScrollInfo *scroll) +{ + graphene_rect_t bounds; + int x, y; + double adj_x, adj_y; + + g_return_if_fail (GTK_IS_VIEWPORT (viewport)); + g_return_if_fail (GTK_IS_WIDGET (descendant)); + + if (!gtk_widget_compute_bounds (descendant, GTK_WIDGET (viewport), &bounds)) + return; + + adj_x = gtk_adjustment_get_value (viewport->adjustment[GTK_ORIENTATION_HORIZONTAL]); + adj_y = gtk_adjustment_get_value (viewport->adjustment[GTK_ORIENTATION_VERTICAL]); + + gtk_scroll_info_compute_scroll (scroll, + &(GdkRectangle) { + floor (bounds.origin.x + adj_x), + floor (bounds.origin.y + adj_y), + ceil (bounds.origin.x + bounds.size.width) - floor (bounds.origin.x), + ceil (bounds.origin.y + bounds.size.height) - floor (bounds.origin.y) + }, + &(GdkRectangle) { + adj_x, + adj_y, + gtk_widget_get_width (GTK_WIDGET (viewport)), + gtk_widget_get_height (GTK_WIDGET (viewport)) + }, + &x, &y); + + gtk_adjustment_animate_to_value (viewport->adjustment[GTK_ORIENTATION_HORIZONTAL], x); + gtk_adjustment_animate_to_value (viewport->adjustment[GTK_ORIENTATION_VERTICAL], y); +} + diff --git a/gtk/gtkviewport.h b/gtk/gtkviewport.h index 5663ebd04b..700dffb86d 100644 --- a/gtk/gtkviewport.h +++ b/gtk/gtkviewport.h @@ -61,6 +61,11 @@ void gtk_viewport_set_child (GtkViewport *viewport, GDK_AVAILABLE_IN_ALL GtkWidget * gtk_viewport_get_child (GtkViewport *viewport); +GDK_AVAILABLE_IN_4_12 +void gtk_viewport_scroll_to (GtkViewport *viewport, + GtkWidget *descendant, + GtkScrollInfo *scroll); + G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkViewport, g_object_unref) G_END_DECLS From 447c07bf5e3802d1e02d230ef8c1e2a99013deb3 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 8 Mar 2023 01:19:39 +0100 Subject: [PATCH 3/8] viewport: Use gtk_viewport_scroll_to() for focus scrolling --- gtk/gtkviewport.c | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/gtk/gtkviewport.c b/gtk/gtkviewport.c index 8fc93718da..73ed29f877 100644 --- a/gtk/gtkviewport.c +++ b/gtk/gtkviewport.c @@ -604,30 +604,12 @@ gtk_viewport_set_scroll_to_focus (GtkViewport *viewport, g_object_notify (G_OBJECT (viewport), "scroll-to-focus"); } -static void -scroll_to_view (GtkAdjustment *adj, - double pos, - double size) -{ - double value, page_size; - - value = gtk_adjustment_get_value (adj); - page_size = gtk_adjustment_get_page_size (adj); - - if (pos < 0) - gtk_adjustment_animate_to_value (adj, value + pos); - else if (pos + size >= page_size) - gtk_adjustment_animate_to_value (adj, value + pos + size - page_size); -} - static void focus_change_handler (GtkWidget *widget) { GtkViewport *viewport = GTK_VIEWPORT (widget); GtkRoot *root; GtkWidget *focus_widget; - graphene_rect_t rect; - graphene_point_t p; if ((gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_FOCUS_WITHIN) == 0) return; @@ -641,16 +623,7 @@ focus_change_handler (GtkWidget *widget) if (GTK_IS_TEXT (focus_widget)) focus_widget = gtk_widget_get_parent (focus_widget); - if (!gtk_widget_compute_bounds (focus_widget, viewport->child, &rect)) - return; - - if (!gtk_widget_compute_point (viewport->child, widget, - &GRAPHENE_POINT_INIT (rect.origin.x, rect.origin.y), - &p)) - return; - - scroll_to_view (viewport->adjustment[GTK_ORIENTATION_HORIZONTAL], p.x, rect.size.width); - scroll_to_view (viewport->adjustment[GTK_ORIENTATION_VERTICAL], p.y, rect.size.height); + gtk_viewport_scroll_to (viewport, focus_widget, NULL); } static void From b64dc75e3a92a739556bf5d397df67fe5443e9ee Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 8 Mar 2023 00:31:33 +0100 Subject: [PATCH 4/8] listview: Implement gtk_list_view_scroll_to() This adds a flags enum so we can also do select/focus at the same time. It's implemented in GtkListBase, so adding support forgridview should be easy. --- gtk/gtkenums.h | 18 ++++++++ gtk/gtklistbase.c | 96 ++++++++++++++++++++++++++++++---------- gtk/gtklistbaseprivate.h | 5 ++- gtk/gtklistview.c | 27 +++++++++++ gtk/gtklistview.h | 5 +++ 5 files changed, 127 insertions(+), 24 deletions(-) diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index 38bb3e25d1..bcc1373a6f 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -295,6 +295,24 @@ typedef enum GTK_LIST_TAB_CELL } GtkListTabBehavior; +/** + * GtkListScrollFlags: + * @GTK_LIST_SCROLL_NONE: Don't do anything extra + * @GTK_LIST_SCROLL_FOCUS: Focus the target item + * @GTK_LIST_SCROLL_SELECT: Select the target item and + * unselect all other items. + * + * List of actions to perform when scrolling to items in + * a list widget. + * + * Since: 4.12 + */ +typedef enum { + GTK_LIST_SCROLL_NONE = 0, + GTK_LIST_SCROLL_FOCUS = 1 << 0, + GTK_LIST_SCROLL_SELECT = 1 << 1 +} GtkListScrollFlags; + /** * GtkMessageType: * @GTK_MESSAGE_INFO: Informational message diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c index 2589af28fa..2cdac1461c 100644 --- a/gtk/gtklistbase.c +++ b/gtk/gtklistbase.c @@ -34,6 +34,7 @@ #include "gtkmultiselection.h" #include "gtkorientable.h" #include "gtkscrollable.h" +#include "gtkscrollinfoprivate.h" #include "gtksingleselection.h" #include "gtksnapshot.h" #include "gtktypebuiltins.h" @@ -528,7 +529,7 @@ gtk_list_base_get_n_items (GtkListBase *self) return g_list_model_get_n_items (G_LIST_MODEL (priv->model)); } -guint +static guint gtk_list_base_get_focus_position (GtkListBase *self) { GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); @@ -818,23 +819,19 @@ gtk_list_base_set_property (GObject *object, } static void -gtk_list_base_compute_scroll_align (GtkListBase *self, - GtkOrientation orientation, - int cell_start, - int cell_end, +gtk_list_base_compute_scroll_align (int cell_start, + int cell_size, + int visible_start, + int visible_size, double current_align, GtkPackType current_side, double *new_align, GtkPackType *new_side) { - int visible_start, visible_size, visible_end; - int cell_size; + int cell_end, visible_end; - gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self), - orientation, - &visible_start, NULL, &visible_size); visible_end = visible_start + visible_size; - cell_size = cell_end - cell_start; + cell_end = cell_start + cell_size; if (cell_size <= visible_size) { @@ -878,26 +875,38 @@ gtk_list_base_compute_scroll_align (GtkListBase *self, } static void -gtk_list_base_scroll_to_item (GtkListBase *self, - guint pos) +gtk_list_base_scroll_to_item (GtkListBase *self, + guint pos, + GtkScrollInfo *scroll) { GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); double align_along, align_across; GtkPackType side_along, side_across; - GdkRectangle area; + GdkRectangle area, viewport; + int x, y; if (!gtk_list_base_get_allocation (GTK_LIST_BASE (self), pos, &area)) - return; + { + g_clear_pointer (&scroll, gtk_scroll_info_unref); + return; + } - gtk_list_base_compute_scroll_align (self, - gtk_list_base_get_orientation (GTK_LIST_BASE (self)), - area.y, area.y + area.height, + gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self), + gtk_list_base_get_orientation (GTK_LIST_BASE (self)), + &viewport.y, NULL, &viewport.height); + gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self), + gtk_list_base_get_opposite_orientation (GTK_LIST_BASE (self)), + &viewport.x, NULL, &viewport.width); + + gtk_scroll_info_compute_scroll (scroll, &area, &viewport, &x, &y); + + gtk_list_base_compute_scroll_align (area.y, area.height, + y, viewport.height, priv->anchor_align_along, priv->anchor_side_along, &align_along, &side_along); - gtk_list_base_compute_scroll_align (self, - gtk_list_base_get_opposite_orientation (GTK_LIST_BASE (self)), - area.x, area.x + area.width, + gtk_list_base_compute_scroll_align (area.x, area.width, + x, viewport.width, priv->anchor_align_across, priv->anchor_side_across, &align_across, &side_across); @@ -905,6 +914,8 @@ gtk_list_base_scroll_to_item (GtkListBase *self, pos, align_across, side_across, align_along, side_along); + + g_clear_pointer (&scroll, gtk_scroll_info_unref); } static void @@ -920,7 +931,7 @@ gtk_list_base_scroll_to_item_action (GtkWidget *widget, g_variant_get (parameter, "u", &pos); - gtk_list_base_scroll_to_item (self, pos); + gtk_list_base_scroll_to_item (self, pos, NULL); } static void @@ -940,7 +951,7 @@ gtk_list_base_set_focus_child (GtkWidget *widget, if (pos != gtk_list_item_tracker_get_position (priv->item_manager, priv->focus)) { - gtk_list_base_scroll_to_item (self, pos); + gtk_list_base_scroll_to_item (self, pos, NULL); gtk_list_item_tracker_set_position (priv->item_manager, priv->focus, pos, @@ -2320,3 +2331,42 @@ gtk_list_base_get_tab_behavior (GtkListBase *self) return priv->tab_behavior; } +void +gtk_list_base_scroll_to (GtkListBase *self, + guint pos, + GtkListScrollFlags flags, + GtkScrollInfo *scroll) +{ + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + + if (flags & GTK_LIST_SCROLL_FOCUS) + { + GtkListItemTracker *old_focus; + + /* We need a tracker here to keep the focus widget around, + * because we need to update the focus tracker before grabbing + * focus, because otherwise gtk_list_base_set_focus_child() will + * scroll to the item, and we want to avoid that. + */ + old_focus = gtk_list_item_tracker_new (priv->item_manager); + gtk_list_item_tracker_set_position (priv->item_manager, old_focus, gtk_list_base_get_focus_position (self), 0, 0); + + gtk_list_item_tracker_set_position (priv->item_manager, priv->focus, pos, 0, 0); + + /* XXX: Is this the proper check? */ + if (gtk_widget_get_state_flags (GTK_WIDGET (self)) & GTK_STATE_FLAG_FOCUS_WITHIN) + { + GtkListTile *tile = gtk_list_item_manager_get_nth (priv->item_manager, pos, NULL); + + gtk_widget_grab_focus (tile->widget); + } + + gtk_list_item_tracker_free (priv->item_manager, old_focus); + } + + if (flags & GTK_LIST_SCROLL_SELECT) + gtk_list_base_select_item (self, pos, FALSE, FALSE); + + gtk_list_base_scroll_to_item (self, pos, scroll); +} + diff --git a/gtk/gtklistbaseprivate.h b/gtk/gtklistbaseprivate.h index fad3872cdc..01afcde913 100644 --- a/gtk/gtklistbaseprivate.h +++ b/gtk/gtklistbaseprivate.h @@ -62,7 +62,6 @@ 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); @@ -95,3 +94,7 @@ GtkListTabBehavior gtk_list_base_get_tab_behavior (GtkListBase void gtk_list_base_allocate (GtkListBase *self); +void gtk_list_base_scroll_to (GtkListBase *self, + guint pos, + GtkListScrollFlags flags, + GtkScrollInfo *scroll); diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index f06b9cafec..53e8558dd8 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -1349,3 +1349,30 @@ gtk_list_view_get_tab_behavior (GtkListView *self) return gtk_list_base_get_tab_behavior (GTK_LIST_BASE (self)); } +/** + * gtk_list_view_scroll_to: + * @self: The listview to scroll in + * @pos: position of the item + * @flags: actions to perform + * @scroll: (nullable) (transfer full): details of how to perform + * the scroll operation or %NULL to scroll into view + * + * Scrolls to the item at the given position and performs the actions + * specified in @flags. + * + * This function works no matter if the listview is shown or focused. + * If it isn't, then the changes will take effect once that happens. + * + * Since: 4.12 + */ +void +gtk_list_view_scroll_to (GtkListView *self, + guint pos, + GtkListScrollFlags flags, + GtkScrollInfo *scroll) +{ + g_return_if_fail (GTK_IS_LIST_VIEW (self)); + + gtk_list_base_scroll_to (GTK_LIST_BASE (self), pos, flags, scroll); +} + diff --git a/gtk/gtklistview.h b/gtk/gtklistview.h index df4901e319..1948f4236c 100644 --- a/gtk/gtklistview.h +++ b/gtk/gtklistview.h @@ -89,6 +89,11 @@ GDK_AVAILABLE_IN_4_12 GtkListTabBehavior gtk_list_view_get_tab_behavior (GtkListView *self); +GDK_AVAILABLE_IN_4_12 +void gtk_list_view_scroll_to (GtkListView *self, + guint pos, + GtkListScrollFlags flags, + GtkScrollInfo *scroll); G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkListView, g_object_unref) From c278c78378bdd24483726afc27bb685a722a827b Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 26 Mar 2023 00:58:20 +0100 Subject: [PATCH 5/8] gtk-demo: Make arrowing in suggestionentry scroll the dropdown --- demos/gtk-demo/suggestionentry.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/gtk-demo/suggestionentry.c b/demos/gtk-demo/suggestionentry.c index d4dd418f20..a9f66126c6 100644 --- a/demos/gtk-demo/suggestionentry.c +++ b/demos/gtk-demo/suggestionentry.c @@ -801,7 +801,7 @@ suggestion_entry_key_pressed (GtkEventControllerKey *controller, selected = matches - 1; } - gtk_single_selection_set_selected (self->selection, selected); + gtk_list_view_scroll_to (GTK_LIST_VIEW (self->list), selected, GTK_LIST_SCROLL_SELECT, NULL); return TRUE; } From 6077ea4f48952039eaee61c94ef25b99484c424f Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 24 May 2023 22:38:36 -0400 Subject: [PATCH 6/8] Add gtk_grid_view_scroll_to This does the same as gtk_list_view_scroll_to, just for a grid view. --- gtk/gtkgridview.c | 26 ++++++++++++++++++++++++++ gtk/gtkgridview.h | 7 +++++++ 2 files changed, 33 insertions(+) diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c index dd36309844..1a9e2a5f3d 100644 --- a/gtk/gtkgridview.c +++ b/gtk/gtkgridview.c @@ -1564,3 +1564,29 @@ gtk_grid_view_get_tab_behavior (GtkGridView *self) return gtk_list_base_get_tab_behavior (GTK_LIST_BASE (self)); } +/** + * gtk_grid_view_scroll_to: + * @self: The gridview to scroll in + * @pos: position of the item + * @flags: actions to perform + * @scroll: (nullable) (transfer full): details of how to perform + * the scroll operation or %NULL to scroll into view + * + * Scrolls to the item at the given position and performs the actions + * specified in @flags. + * + * This function works no matter if the gridview is shown or focused. + * If it isn't, then the changes will take effect once that happens. + * + * Since: 4.12 + */ +void +gtk_grid_view_scroll_to (GtkGridView *self, + guint pos, + GtkListScrollFlags flags, + GtkScrollInfo *scroll) +{ + g_return_if_fail (GTK_IS_GRID_VIEW (self)); + + gtk_list_base_scroll_to (GTK_LIST_BASE (self), pos, flags, scroll); +} diff --git a/gtk/gtkgridview.h b/gtk/gtkgridview.h index 9525c07c7b..6e4b70f940 100644 --- a/gtk/gtkgridview.h +++ b/gtk/gtkgridview.h @@ -85,6 +85,13 @@ void gtk_grid_view_set_single_click_activate (GtkGridView GDK_AVAILABLE_IN_ALL gboolean gtk_grid_view_get_single_click_activate (GtkGridView *self); +GDK_AVAILABLE_IN_4_12 +void gtk_grid_view_scroll_to (GtkGridView *self, + guint pos, + GtkListScrollFlags flags, + GtkScrollInfo *scroll); + + G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkGridView, g_object_unref) G_END_DECLS From 2120ef38d9e20041169c0e94410f73cd97685622 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 8 Mar 2023 00:53:14 +0100 Subject: [PATCH 7/8] columnview: Implement gtk_column_view_scroll_to() It's basically the listview version, but with an (optional) column to do cell-based scrolling/focusing. --- gtk/gtkcolumnview.c | 70 +++++++++++++++++++++++++++++------- gtk/gtkcolumnview.h | 8 ++++- gtk/gtkcolumnviewprivate.h | 3 +- gtk/gtkcolumnviewrowwidget.c | 7 ++-- 4 files changed, 71 insertions(+), 17 deletions(-) diff --git a/gtk/gtkcolumnview.c b/gtk/gtkcolumnview.c index 8a87f758dd..c3db9d0e7f 100644 --- a/gtk/gtkcolumnview.c +++ b/gtk/gtkcolumnview.c @@ -35,6 +35,7 @@ #include "gtkgesturedrag.h" #include "gtklistviewprivate.h" #include "gtkscrollable.h" +#include "gtkscrollinfoprivate.h" #include "gtksizerequest.h" #include "gtktypebuiltins.h" #include "gtkwidgetprivate.h" @@ -1722,7 +1723,7 @@ gtk_column_view_remove_column (GtkColumnView *self, else item = NULL; - gtk_column_view_set_focus_column (self, item); + gtk_column_view_set_focus_column (self, item, TRUE); } } @@ -1782,23 +1783,27 @@ gtk_column_view_insert_column (GtkColumnView *self, static void gtk_column_view_scroll_to_column (GtkColumnView *self, - GtkColumnViewColumn *column) + GtkColumnViewColumn *column, + GtkScrollInfo *scroll_info) { - int col_x, col_width, adj_x, adj_width; + int col_x, col_width, new_value; gtk_column_view_column_get_header_allocation (column, &col_x, &col_width); - adj_x = gtk_adjustment_get_value (self->hadjustment); - adj_width = gtk_adjustment_get_page_size (self->hadjustment); - if (col_x < adj_x) - gtk_adjustment_set_value (self->hadjustment, col_x); - else if (col_x + col_width > adj_x + adj_width) - gtk_adjustment_set_value (self->hadjustment, adj_x + adj_width - col_width); + new_value = gtk_scroll_info_compute_for_orientation (scroll_info, + GTK_ORIENTATION_HORIZONTAL, + col_x, + col_width, + gtk_adjustment_get_value (self->hadjustment), + gtk_adjustment_get_page_size (self->hadjustment)); + + gtk_adjustment_set_value (self->hadjustment, new_value); } void gtk_column_view_set_focus_column (GtkColumnView *self, - GtkColumnViewColumn *column) + GtkColumnViewColumn *column, + gboolean scroll) { g_assert (column == NULL || gtk_column_view_column_get_column_view (column) == self); @@ -1807,8 +1812,8 @@ gtk_column_view_set_focus_column (GtkColumnView *self, self->focus_column = column; - if (column) - gtk_column_view_scroll_to_column (self, column); + if (column && scroll) + gtk_column_view_scroll_to_column (self, column, NULL); } GtkColumnViewColumn * @@ -2174,3 +2179,44 @@ gtk_column_view_set_header_factory (GtkColumnView *self, g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HEADER_FACTORY]); } +/** + * gtk_column_view_scroll_to: + * @self: The columnview to scroll in + * @pos: position of the item + * @column: (nullable) (transfer none): The column to scroll to + * or %NULL to not scroll columns. + * @flags: actions to perform + * @scroll: (nullable) (transfer full): details of how to perform + * the scroll operation or %NULL to scroll into view + * + * Scroll to the row at the given position - or cell if a column is + * given - and performs the actions specified in @flags. + * + * This function works no matter if the listview is shown or focused. + * If it isn't, then the changes will take effect once that happens. + * + * Since: 4.12 + */ +void +gtk_column_view_scroll_to (GtkColumnView *self, + guint pos, + GtkColumnViewColumn *column, + GtkListScrollFlags flags, + GtkScrollInfo *scroll) +{ + g_return_if_fail (GTK_IS_COLUMN_VIEW (self)); + g_return_if_fail (column == NULL || GTK_IS_COLUMN_VIEW_COLUMN (column)); + if (column) + { + g_return_if_fail (gtk_column_view_column_get_column_view (column) == self); + } + + if (column && (flags & GTK_LIST_SCROLL_FOCUS)) + gtk_column_view_set_focus_column (self, column, FALSE); + + gtk_list_view_scroll_to (self->listview, pos, flags, scroll); + + if (column) + gtk_column_view_scroll_to_column (self, column, scroll); +} + diff --git a/gtk/gtkcolumnview.h b/gtk/gtkcolumnview.h index c80c169896..d15c35d7fc 100644 --- a/gtk/gtkcolumnview.h +++ b/gtk/gtkcolumnview.h @@ -122,7 +122,6 @@ GDK_AVAILABLE_IN_4_12 GtkListItemFactory * gtk_column_view_get_row_factory (GtkColumnView *self); - GDK_AVAILABLE_IN_4_12 void gtk_column_view_set_header_factory (GtkColumnView *self, GtkListItemFactory *factory); @@ -130,5 +129,12 @@ GDK_AVAILABLE_IN_4_12 GtkListItemFactory * gtk_column_view_get_header_factory (GtkColumnView *self); +GDK_AVAILABLE_IN_4_12 +void gtk_column_view_scroll_to (GtkColumnView *self, + guint pos, + GtkColumnViewColumn *column, + GtkListScrollFlags flags, + GtkScrollInfo *scroll); + G_END_DECLS diff --git a/gtk/gtkcolumnviewprivate.h b/gtk/gtkcolumnviewprivate.h index 1a3061e332..f92b6da36a 100644 --- a/gtk/gtkcolumnviewprivate.h +++ b/gtk/gtkcolumnviewprivate.h @@ -40,6 +40,7 @@ void gtk_column_view_distribute_width (GtkColumnView GtkRequestedSize *sizes); void gtk_column_view_set_focus_column (GtkColumnView *self, - GtkColumnViewColumn *focus_column); + GtkColumnViewColumn *focus_column, + gboolean scroll); GtkColumnViewColumn * gtk_column_view_get_focus_column (GtkColumnView *self); diff --git a/gtk/gtkcolumnviewrowwidget.c b/gtk/gtkcolumnviewrowwidget.c index de71a721f5..3febbf813c 100644 --- a/gtk/gtkcolumnviewrowwidget.c +++ b/gtk/gtkcolumnviewrowwidget.c @@ -288,7 +288,7 @@ gtk_column_view_row_widget_focus (GtkWidget *widget, { if (gtk_widget_grab_focus_self (widget)) { - gtk_column_view_set_focus_column (view, NULL); + gtk_column_view_set_focus_column (view, NULL, FALSE); return TRUE; } } @@ -323,7 +323,7 @@ gtk_column_view_row_widget_grab_focus (GtkWidget *widget) if (gtk_widget_grab_focus_self (widget)) { - gtk_column_view_set_focus_column (view, NULL); + gtk_column_view_set_focus_column (view, NULL, FALSE); return TRUE; } @@ -354,7 +354,8 @@ gtk_column_view_row_widget_set_focus_child (GtkWidget *widget, if (child) { gtk_column_view_set_focus_column (gtk_column_view_row_widget_get_column_view (self), - gtk_column_view_row_child_get_column (child)); + gtk_column_view_row_child_get_column (child), + TRUE); } } From a447c439f4143298ff055cf373a5a13ff1eaf1e6 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 8 Mar 2023 00:53:51 +0100 Subject: [PATCH 8/8] inspector: Use scroll_to() in the object tree search --- gtk/inspector/object-tree.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/gtk/inspector/object-tree.c b/gtk/inspector/object-tree.c index 0c0c9e28ab..b86f9bcbe5 100644 --- a/gtk/inspector/object-tree.c +++ b/gtk/inspector/object-tree.c @@ -910,7 +910,11 @@ search (GtkInspectorObjectTree *wt, { if (match_object (child, text)) { - gtk_single_selection_set_selected (priv->selection, row); + gtk_column_view_scroll_to (GTK_COLUMN_VIEW (wt->priv->list), + row, + NULL, + GTK_LIST_SCROLL_SELECT | GTK_LIST_SCROLL_FOCUS, + NULL); g_object_unref (child); g_object_unref (row_item); return TRUE; @@ -1302,8 +1306,12 @@ gtk_inspector_object_tree_select_object (GtkInspectorObjectTree *wt, if (row_item == NULL) return; - gtk_single_selection_set_selected (wt->priv->selection, - gtk_tree_list_row_get_position (row_item)); + gtk_column_view_scroll_to (GTK_COLUMN_VIEW (wt->priv->list), + gtk_tree_list_row_get_position (row_item), + NULL, + GTK_LIST_SCROLL_SELECT | GTK_LIST_SCROLL_FOCUS, + NULL); + g_signal_emit (wt, signals[OBJECT_SELECTED], 0, object); // FIXME g_object_unref (row_item); }