Compare commits

...

2 Commits

Author SHA1 Message Date
Alexander Mikhaylenko
41dcb14427 scrolledwindow: Support nested scrolling
While it's possible to scroll GtkScrolledWindow with touchscreen gestures,
currently it doesn't work for touchpad: GtkScrolledWindow never propagates
scroll events and if multiple scrolled windows are nested, only the inner
one will receive any events.

Since e9fe270e94, GtkScrolledWindow handles
only full scroll sequences, but still accepts any scroll sequence, causing
issues with nested scrolling.

Take another step and keep track of the sequence state, checking the
direction when a smooth scroll starts and only claiming the sequence if
the scrolled window can scroll in the direction of the current scrolling,
and otherwise denying it and ignoring any further events until it ends.

The check must be done on the bubble phase because if there are nested
scrolled windows that can scroll in the same direction, we want to pick
the inner one and not the outer one. The check also has to be done in the
scroll signal handler, since we don't have scroll deltas available in
scroll-begin. After we determine the direction and claim the sequence, we
can switch to the capture phase and proceed with scrolling as usual.

This also means that there are 3 states of the sequence state: a sequence
can be claimed, denied, or during the first invocation of the scroll signal
handler it's undecided. This neatly maps to GtkEventSequenceState, so we
can reuse it.
2020-12-15 21:21:49 +05:00
Alexander Mikhaylenko
29c49e085d tests: Add a nested scrolling test 2020-12-14 18:53:00 +05:00
3 changed files with 154 additions and 12 deletions

View File

@@ -271,6 +271,8 @@ typedef struct
guint propagate_natural_height : 1;
guint smooth_scroll : 1;
GtkEventSequenceState scroll_state;
int min_content_width;
int min_content_height;
int max_content_width;
@@ -408,10 +410,10 @@ static void indicator_set_over (Indicator *indicator,
static void install_scroll_cursor (GtkScrolledWindow *scrolled_window);
static void uninstall_scroll_cursor (GtkScrolledWindow *scrolled_window);
static void scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
double delta_x,
double delta_y,
GtkEventControllerScroll *scroll);
static gboolean scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
double delta_x,
double delta_y,
GtkEventControllerScroll *scroll);
static guint signals[LAST_SIGNAL] = {0};
static GParamSpec *properties[NUM_PROPERTIES];
@@ -1230,11 +1232,8 @@ captured_scroll_cb (GtkEventControllerScroll *scroll,
gtk_scrolled_window_cancel_deceleration (scrolled_window);
if (priv->smooth_scroll)
{
scrolled_window_scroll (scrolled_window, delta_x, delta_y, scroll);
return GDK_EVENT_STOP;
}
if (priv->smooth_scroll && priv->scroll_state != GTK_EVENT_SEQUENCE_DENIED)
return scrolled_window_scroll (scrolled_window, delta_x, delta_y, scroll);
return GDK_EVENT_PROPAGATE;
}
@@ -1319,7 +1318,7 @@ scroll_controller_scroll_begin (GtkEventControllerScroll *scroll,
priv->smooth_scroll = TRUE;
}
static void
static gboolean
scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
double delta_x,
double delta_y,
@@ -1333,6 +1332,18 @@ scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (scroll));
shifted = (state & GDK_SHIFT_MASK) != 0;
if (priv->smooth_scroll && priv->scroll_state == GTK_EVENT_SEQUENCE_NONE)
{
if ((may_hscroll (scrolled_window) && ABS (delta_x) > ABS (delta_y)) ||
(may_vscroll (scrolled_window) && ABS (delta_y) >= ABS (delta_x)))
priv->scroll_state = GTK_EVENT_SEQUENCE_CLAIMED;
else
priv->scroll_state = GTK_EVENT_SEQUENCE_DENIED;
}
if (priv->smooth_scroll && priv->scroll_state == GTK_EVENT_SEQUENCE_DENIED)
return GDK_EVENT_PROPAGATE;
gtk_scrolled_window_invalidate_overshoot (scrolled_window);
if (shifted)
@@ -1388,6 +1399,8 @@ scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
g_source_set_name_by_id (priv->scroll_events_overshoot_id,
"[gtk] start_scroll_deceleration_cb");
}
return GDK_EVENT_STOP;
}
static gboolean
@@ -1399,8 +1412,8 @@ scroll_controller_scroll (GtkEventControllerScroll *scroll,
GtkScrolledWindowPrivate *priv =
gtk_scrolled_window_get_instance_private (scrolled_window);
if (!priv->smooth_scroll)
scrolled_window_scroll (scrolled_window, delta_x, delta_y, scroll);
if (!priv->smooth_scroll || priv->scroll_state == GTK_EVENT_SEQUENCE_NONE)
return scrolled_window_scroll (scrolled_window, delta_x, delta_y, scroll);
return GDK_EVENT_STOP;
}
@@ -1412,6 +1425,7 @@ scroll_controller_scroll_end (GtkEventControllerScroll *scroll,
GtkScrolledWindowPrivate *priv = gtk_scrolled_window_get_instance_private (scrolled_window);
priv->smooth_scroll = FALSE;
priv->scroll_state = GTK_EVENT_SEQUENCE_NONE;
uninstall_scroll_cursor (scrolled_window);
}
@@ -1421,10 +1435,13 @@ scroll_controller_decelerate (GtkEventControllerScroll *scroll,
double initial_vel_y,
GtkScrolledWindow *scrolled_window)
{
GtkScrolledWindowPrivate *priv = gtk_scrolled_window_get_instance_private (scrolled_window);
double unit_x, unit_y;
gboolean shifted;
GdkModifierType state;
if (priv->scroll_state != GTK_EVENT_SEQUENCE_CLAIMED)
return;
state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (scroll));
@@ -2003,6 +2020,8 @@ gtk_scrolled_window_init (GtkScrolledWindow *scrolled_window)
priv->max_content_width = -1;
priv->max_content_height = -1;
priv->scroll_state = GTK_EVENT_SEQUENCE_NONE;
priv->overlay_scrolling = TRUE;
priv->drag_gesture = gtk_gesture_drag_new ();

View File

@@ -59,6 +59,7 @@ gtk_tests = [
['testlockbutton'],
['testmenubutton'],
['testmountoperation'],
['testnestedscrolling'],
['testnotebookdnd'],
['testnouiprint'],
['testoverlay'],

122
tests/testnestedscrolling.c Normal file
View File

@@ -0,0 +1,122 @@
#include <gtk/gtk.h>
/* This is the function that creates the #GListModel that we need.
* GTK list widgets need a #GListModel to display, as models support change
* notifications.
* Unfortunately various older APIs do not provide list models, so we create
* our own.
*/
static GListModel *
create_application_list (void)
{
GListStore *store;
GList *apps, *l;
/* We use a #GListStore here, which is a simple array-like list implementation
* for manual management.
* List models need to know what type of data they provide, so we need to
* provide the type here. As we want to do a list of applications, #GAppInfo
* is the object we provide.
*/
store = g_list_store_new (G_TYPE_APP_INFO);
apps = g_app_info_get_all ();
for (l = apps; l; l = l->next)
g_list_store_append (store, l->data);
g_list_free_full (apps, g_object_unref);
return G_LIST_MODEL (store);
}
static void
setup_list_item (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label = gtk_label_new (NULL);
gtk_widget_set_margin_top (label, 6);
gtk_widget_set_margin_bottom (label, 6);
gtk_list_item_set_child (list_item, label);
}
static void
bind_list_item (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
gpointer item;
const char *s;
item = gtk_list_item_get_item (list_item);
if (item)
s = g_app_info_get_name (G_APP_INFO (item));
else
s = NULL;
label = gtk_list_item_get_child (list_item);
gtk_label_set_text (GTK_LABEL (label), s);
}
static void
on_application_activate (GtkApplication *app)
{
GtkWidget *window, *swindow, *box;
gint i = 0;
window = gtk_application_window_new (app);
gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
swindow = gtk_scrolled_window_new ();
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swindow), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
gtk_window_set_child (GTK_WINDOW (window), swindow);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (swindow), box);
for (i = 0; i < 20; i++)
{
GtkWidget *list, *separator;
GtkSelectionModel *model;
GtkListItemFactory *factory;
if (i > 0)
{
separator = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
gtk_box_append (GTK_BOX (box), separator);
}
swindow = gtk_scrolled_window_new ();
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_box_append (GTK_BOX (box), swindow);
model = GTK_SELECTION_MODEL (gtk_single_selection_new (create_application_list ()));
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", G_CALLBACK (setup_list_item), NULL);
g_signal_connect (factory, "bind", G_CALLBACK (bind_list_item), NULL);
list = gtk_list_view_new (model, factory);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (swindow), list);
}
gtk_window_present (GTK_WINDOW (window));
}
int
main (int argc, char *argv[])
{
GtkApplication *application = gtk_application_new ("org.gtk.test.nestedscrolling",
G_APPLICATION_FLAGS_NONE);
int result;
g_signal_connect (application, "activate",
G_CALLBACK (on_application_activate), NULL);
result = g_application_run (G_APPLICATION (application), argc, argv);
g_object_unref (application);
return result;
}