From 3cbf1845a97690f6c006fc839442c6dc909e4959 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 8 Sep 2019 18:45:58 -0400 Subject: [PATCH] Add a timeout for open submenus When a popover menu has an open submenu, delay activating another item until after the pointer is stationary for a little while. This avoids the need for precise horizontal motion when moving towards the submenu. --- gtk/gtkmodelbutton.c | 83 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 15 deletions(-) diff --git a/gtk/gtkmodelbutton.c b/gtk/gtkmodelbutton.c index 98d70e2226..a2ae000df2 100644 --- a/gtk/gtkmodelbutton.c +++ b/gtk/gtkmodelbutton.c @@ -171,6 +171,7 @@ struct _GtkModelButton GtkButtonRole role; GtkSizeGroup *indicators; char *accel; + guint open_timeout; }; typedef GtkButtonClass GtkModelButtonClass; @@ -935,8 +936,6 @@ close_menu (GtkModelButton *button) } } -static void open_submenu (GtkPopover *popover); - static void gtk_model_button_clicked (GtkButton *button) { @@ -973,6 +972,9 @@ gtk_model_button_finalize (GObject *object) g_free (button->accel); g_clear_pointer (&button->popover, gtk_widget_unparent); + if (button->open_timeout) + g_source_remove (button->open_timeout); + G_OBJECT_CLASS (gtk_model_button_parent_class)->finalize (object); } @@ -1226,31 +1228,60 @@ close_submenus (GtkPopover *popover) } } -static void -open_submenu (GtkPopover *popover) +static gboolean +open_submenu (gpointer data) { + GtkModelButton *button = data; + GtkPopover *popover; + + popover = (GtkPopover*)gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_POPOVER); + if (GTK_IS_POPOVER_MENU (popover)) { - GtkWidget *active_item; + gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), GTK_WIDGET (button)); - active_item = gtk_popover_menu_get_active_item (GTK_POPOVER_MENU (popover)); - if (GTK_IS_MODEL_BUTTON (active_item) && - GTK_MODEL_BUTTON (active_item)->popover) + if (button->popover) { - GtkWidget *submenu; + GtkWidget *submenu = button->popover; - submenu = GTK_MODEL_BUTTON (active_item)->popover; if (gtk_popover_menu_get_open_submenu (GTK_POPOVER_MENU (popover)) != submenu) -{ -g_print ("close submenus %p %p\n", gtk_popover_menu_get_open_submenu (GTK_POPOVER_MENU (popover)), submenu); close_submenus (popover); -} gtk_popover_popup (GTK_POPOVER (submenu)); gtk_popover_menu_set_open_submenu (GTK_POPOVER_MENU (popover), submenu); gtk_popover_menu_set_parent_menu (GTK_POPOVER_MENU (submenu), GTK_WIDGET (popover)); } } + + button->open_timeout = 0; + + return G_SOURCE_REMOVE; +} + +#define OPEN_TIMEOUT 80 + +static void +start_open (GtkModelButton *button) +{ + if (button->open_timeout) + g_source_remove (button->open_timeout); + + if (button->popover && + gtk_widget_get_visible (button->popover)) + return; + + button->open_timeout = g_timeout_add (OPEN_TIMEOUT, open_submenu, button); + g_source_set_name_by_id (button->open_timeout, "[gtk] open_submenu"); +} + +static void +stop_open (GtkModelButton *button) +{ + if (button->open_timeout) + { + g_source_remove (button->open_timeout); + button->open_timeout = 0; + } } static void @@ -1276,11 +1307,31 @@ enter_cb (GtkEventController *controller, if (popover && (is || contains)) { - gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), target); - open_submenu (GTK_POPOVER (popover)); + if (gtk_popover_menu_get_open_submenu (GTK_POPOVER_MENU (popover)) != NULL) + start_open (GTK_MODEL_BUTTON (target)); + else + open_submenu (target); } } +static void +motion_cb (GtkEventController *controller, + double x, + double y, + gpointer data) +{ + start_open (GTK_MODEL_BUTTON (data)); +} + +static void +leave_cb (GtkEventController *controller, + GdkCrossingMode mode, + GdkNotifyType type, + gpointer data) +{ + stop_open (GTK_MODEL_BUTTON (data)); +} + static void focus_in_cb (GtkEventController *controller, GdkCrossingMode mode, @@ -1336,6 +1387,8 @@ gtk_model_button_init (GtkModelButton *button) controller = gtk_event_controller_motion_new (); g_signal_connect (controller, "enter", G_CALLBACK (enter_cb), button); + g_signal_connect (controller, "motion", G_CALLBACK (motion_cb), button); + g_signal_connect (controller, "leave", G_CALLBACK (leave_cb), button); gtk_widget_add_controller (GTK_WIDGET (button), controller); controller = gtk_event_controller_key_new ();