[gnome-todo] task-list-view: scroll when dragging over the tasklist borders



commit 4799bc777ce58f56bbb4a6a40ce1db1b157b7ab7
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Tue Oct 25 23:14:51 2016 -0200

    task-list-view: scroll when dragging over the tasklist borders
    
    When scrolling over the top and bottom areas, we should scroll the
    tasklist widget, otherwise some (many?) tasks will be hidden and
    unaccessible.
    
    This also has the nice side-effect of fixing the flickering when
    moving a task around.

 src/gtd-dnd-row.c        |  148 +++++++++++++++++++---------------------------
 src/gtd-dnd-row.h        |    2 -
 src/gtd-task-list-view.c |   99 ++++++++++++++++++++++++++++--
 3 files changed, 153 insertions(+), 96 deletions(-)
---
diff --git a/src/gtd-dnd-row.c b/src/gtd-dnd-row.c
index 4d9747b..f1b6580 100644
--- a/src/gtd-dnd-row.c
+++ b/src/gtd-dnd-row.c
@@ -33,7 +33,6 @@ struct _GtdDndRow
 
   GtdTaskRow         *row_above;
   gint                depth;
-  gboolean            has_dnd : 1;
 };
 
 G_DEFINE_TYPE (GtdDndRow, gtd_dnd_row, GTK_TYPE_LIST_BOX_ROW)
@@ -117,13 +116,69 @@ gtd_dnd_row_set_property (GObject      *object,
 }
 
 static void
-gtd_dnd_row_drag_leave (GtkWidget      *widget,
-                        GdkDragContext *context,
-                        guint           time)
+gtd_dnd_row_class_init (GtdDndRowClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtd_dnd_row_finalize;
+  object_class->get_property = gtd_dnd_row_get_property;
+  object_class->set_property = gtd_dnd_row_set_property;
+
+  properties[PROP_ROW_ABOVE] = g_param_spec_object ("row-above",
+                                                    "Row above",
+                                                    "The task row above this row",
+                                                    GTD_TYPE_TASK_ROW,
+                                                    G_PARAM_READWRITE);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/todo/ui/dnd-row.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, GtdDndRow, box);
+  gtk_widget_class_bind_template_child (widget_class, GtdDndRow, icon);
+
+  gtk_widget_class_set_css_name (widget_class, "dndrow");
+}
+
+static void
+gtd_dnd_row_init (GtdDndRow *self)
 {
-  GtdDndRow *self = GTD_DND_ROW (widget);
+  gtk_widget_init_template (GTK_WIDGET (self));
 
-  self->has_dnd = FALSE;
+  gtk_drag_dest_set (GTK_WIDGET (self),
+                     0,
+                     NULL,
+                     0,
+                     GDK_ACTION_MOVE);
+}
+
+GtkWidget*
+gtd_dnd_row_new (void)
+{
+  return g_object_new (GTD_TYPE_DND_ROW, NULL);
+}
+
+GtdTaskRow*
+gtd_dnd_row_get_row_above (GtdDndRow *self)
+{
+  g_return_val_if_fail (GTD_IS_DND_ROW (self), NULL);
+
+  return self->row_above;
+}
+
+void
+gtd_dnd_row_set_row_above (GtdDndRow  *self,
+                           GtdTaskRow *row)
+{
+  g_return_if_fail (GTD_IS_DND_ROW (self));
+
+  if (g_set_object (&self->row_above, row))
+    {
+      update_row_padding (self);
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ROW_ABOVE]);
+    }
 }
 
 gboolean
@@ -153,8 +208,6 @@ gtd_dnd_row_drag_motion (GtkWidget      *widget,
       self->depth = 0;
     }
 
-  self->has_dnd = TRUE;
-
   update_row_padding (self);
 
   gdk_drag_status (context, GDK_ACTION_COPY, time);
@@ -175,7 +228,6 @@ gtd_dnd_row_drag_drop (GtkWidget      *widget,
   GtdTask *row_task, *target_task;
 
   self = GTD_DND_ROW (widget);
-  self->has_dnd = FALSE;
 
   /* Reset padding */
   update_row_padding (self);
@@ -240,81 +292,3 @@ gtd_dnd_row_drag_drop (GtkWidget      *widget,
   return TRUE;
 }
 
-static void
-gtd_dnd_row_class_init (GtdDndRowClass *klass)
-{
-  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  object_class->finalize = gtd_dnd_row_finalize;
-  object_class->get_property = gtd_dnd_row_get_property;
-  object_class->set_property = gtd_dnd_row_set_property;
-
-  widget_class->drag_drop = gtd_dnd_row_drag_drop;
-  widget_class->drag_leave = gtd_dnd_row_drag_leave;
-  widget_class->drag_motion = gtd_dnd_row_drag_motion;
-
-  properties[PROP_ROW_ABOVE] = g_param_spec_object ("row-above",
-                                                    "Row above",
-                                                    "The task row above this row",
-                                                    GTD_TYPE_TASK_ROW,
-                                                    G_PARAM_READWRITE);
-
-  g_object_class_install_properties (object_class, N_PROPS, properties);
-
-  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/todo/ui/dnd-row.ui");
-
-  gtk_widget_class_bind_template_child (widget_class, GtdDndRow, box);
-  gtk_widget_class_bind_template_child (widget_class, GtdDndRow, icon);
-
-  gtk_widget_class_set_css_name (widget_class, "dndrow");
-}
-
-static void
-gtd_dnd_row_init (GtdDndRow *self)
-{
-  gtk_widget_init_template (GTK_WIDGET (self));
-
-  gtk_drag_dest_set (GTK_WIDGET (self),
-                     0,
-                     NULL,
-                     0,
-                     GDK_ACTION_MOVE);
-}
-
-GtkWidget*
-gtd_dnd_row_new (void)
-{
-  return g_object_new (GTD_TYPE_DND_ROW, NULL);
-}
-
-GtdTaskRow*
-gtd_dnd_row_get_row_above (GtdDndRow *self)
-{
-  g_return_val_if_fail (GTD_IS_DND_ROW (self), NULL);
-
-  return self->row_above;
-}
-
-void
-gtd_dnd_row_set_row_above (GtdDndRow  *self,
-                           GtdTaskRow *row)
-{
-  g_return_if_fail (GTD_IS_DND_ROW (self));
-
-  if (g_set_object (&self->row_above, row))
-    {
-      update_row_padding (self);
-
-      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ROW_ABOVE]);
-    }
-}
-
-gboolean
-gtd_dnd_row_has_dnd (GtdDndRow *self)
-{
-  g_return_val_if_fail (GTD_IS_DND_ROW (self), FALSE);
-
-  return self->has_dnd;
-}
-
diff --git a/src/gtd-dnd-row.h b/src/gtd-dnd-row.h
index d52775d..4c6da9b 100644
--- a/src/gtd-dnd-row.h
+++ b/src/gtd-dnd-row.h
@@ -49,8 +49,6 @@ gboolean             gtd_dnd_row_drag_motion                     (GtkWidget
                                                                   gint                y,
                                                                   guint               time);
 
-gboolean             gtd_dnd_row_has_dnd                         (GtdDndRow          *self);
-
 G_END_DECLS
 
 #endif /* GTD_DND_ROW_H */
diff --git a/src/gtd-task-list-view.c b/src/gtd-task-list-view.c
index a816051..54ea640 100644
--- a/src/gtd-task-list-view.c
+++ b/src/gtd-task-list-view.c
@@ -73,7 +73,8 @@ typedef struct
   GtkRevealer           *revealer;
   GtkImage              *done_image;
   GtkLabel              *done_label;
-  GtkScrolledWindow     *viewport;
+  GtkWidget             *viewport;
+  GtkWidget             *scrolled_window;
 
   /* internal */
   gboolean               can_toggle;
@@ -85,6 +86,10 @@ typedef struct
   GtdTaskList           *task_list;
   GDateTime             *default_date;
 
+  /* DnD autoscroll */
+  guint                  scroll_timeout_id;
+  gboolean               scroll_up : 1;
+
   /* color provider */
   GtkCssProvider        *color_provider;
   GdkRGBA               *color;
@@ -114,6 +119,8 @@ struct _GtdTaskListView
 
 #define TASK_REMOVED_NOTIFICATION_ID             "task-removed-id"
 
+#define DND_SCROLL_OFFSET                        24 // px
+
 /* prototypes */
 static void             gtd_task_list_view__clear_completed_tasks    (GSimpleAction     *simple,
                                                                       GVariant          *parameter,
@@ -1123,7 +1130,7 @@ gtd_task_list_view_constructed (GObject *object)
   /* css provider */
   self->priv->color_provider = gtk_css_provider_new ();
 
-  gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (self->priv->viewport)),
+  gtk_style_context_add_provider (gtk_widget_get_style_context (self->priv->viewport),
                                   GTK_STYLE_PROVIDER (self->priv->color_provider),
                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 2);
 
@@ -1137,6 +1144,70 @@ gtd_task_list_view_constructed (GObject *object)
 /*
  * Listbox Drag n' Drop functions
  */
+static inline gboolean
+scroll_to_dnd (gpointer user_data)
+{
+  GtdTaskListViewPrivate *priv;
+  GtkAdjustment *vadjustment;
+  gint value;
+
+  priv = gtd_task_list_view_get_instance_private (user_data);
+  vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scrolled_window));
+  value = gtk_adjustment_get_value (vadjustment) + (priv->scroll_up ? -6 : 6);
+
+  gtk_adjustment_set_value (vadjustment,
+                            CLAMP (value, 0, gtk_adjustment_get_upper (vadjustment)));
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+check_dnd_scroll (GtdTaskListView *self,
+                  gboolean         should_cancel,
+                  gint             y)
+{
+  GtdTaskListViewPrivate *priv = gtd_task_list_view_get_instance_private (self);
+  gint current_y, height;
+
+  if (should_cancel)
+    {
+      if (priv->scroll_timeout_id > 0)
+        {
+          g_source_remove (priv->scroll_timeout_id);
+          priv->scroll_timeout_id = 0;
+        }
+
+      return;
+    }
+
+  height = gtk_widget_get_allocated_height (priv->scrolled_window);
+  gtk_widget_translate_coordinates (GTK_WIDGET (priv->listbox),
+                                    priv->scrolled_window,
+                                    0, y,
+                                    NULL, &current_y);
+
+  if (current_y < DND_SCROLL_OFFSET || current_y > height - DND_SCROLL_OFFSET)
+    {
+      if (priv->scroll_timeout_id > 0)
+        return;
+
+      /* Start the autoscroll */
+      priv->scroll_up = current_y < DND_SCROLL_OFFSET;
+      priv->scroll_timeout_id = g_timeout_add (25,
+                                               scroll_to_dnd,
+                                               self);
+    }
+  else
+    {
+      if (priv->scroll_timeout_id == 0)
+        return;
+
+      /* Cancel the autoscroll */
+      g_source_remove (priv->scroll_timeout_id);
+      priv->scroll_timeout_id = 0;
+    }
+}
+
 static void
 listbox_drag_leave (GtkListBox      *listbox,
                     GdkDragContext  *context,
@@ -1147,7 +1218,9 @@ listbox_drag_leave (GtkListBox      *listbox,
 
   priv = gtd_task_list_view_get_instance_private (self);
 
-  gtk_widget_set_visible (priv->dnd_row, gtd_dnd_row_has_dnd (GTD_DND_ROW (priv->dnd_row)));
+  gtk_widget_set_visible (priv->dnd_row, FALSE);
+
+  check_dnd_scroll (self, TRUE, -1);
 
   gtk_list_box_invalidate_sort (listbox);
 }
@@ -1173,7 +1246,7 @@ listbox_drag_motion (GtkListBox      *listbox,
    * drop target. Otherwise, the user can drop at the space after the rows, and the row
    * that started the DnD operation is hidden forever.
    */
-  if (!hovered_row || GTD_IS_DND_ROW (hovered_row))
+  if (!hovered_row)
     {
       gtk_widget_hide (priv->dnd_row);
       gtd_dnd_row_set_row_above (GTD_DND_ROW (priv->dnd_row), NULL);
@@ -1181,6 +1254,13 @@ listbox_drag_motion (GtkListBox      *listbox,
       goto success;
     }
 
+  /*
+   * Hovering the DnD row is perfectly valid, but we don't gather the
+   * related row - simply succeed.
+   */
+  if (GTD_IS_DND_ROW (hovered_row))
+    goto success;
+
   row_above_dnd = NULL;
   task_row = GTD_TASK_ROW (hovered_row);
   row_height = gtk_widget_get_allocated_height (GTK_WIDGET (hovered_row));
@@ -1252,6 +1332,7 @@ listbox_drag_motion (GtkListBox      *listbox,
 
   gtd_dnd_row_set_row_above (GTD_DND_ROW (priv->dnd_row), row_above_dnd);
 
+success:
   /*
    * Also pass the current motion to the DnD row, so it correctly
    * adjusts itself - even when the DnD is hovering another row.
@@ -1262,12 +1343,13 @@ listbox_drag_motion (GtkListBox      *listbox,
                            y,
                            time);
 
-success:
+  check_dnd_scroll (self, FALSE, y);
+
   gdk_drag_status (context, GDK_ACTION_COPY, time);
+
   return TRUE;
 
 fail:
-  gdk_drag_status (context, 0, time);
   return FALSE;
 }
 
@@ -1289,6 +1371,8 @@ listbox_drag_drop (GtkWidget       *widget,
                          y,
                          time);
 
+  check_dnd_scroll (self, TRUE, -1);
+
   return TRUE;
 }
 
@@ -1413,6 +1497,7 @@ gtd_task_list_view_class_init (GtdTaskListViewClass *klass)
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, done_label);
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, new_task_row);
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, viewport);
+  gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, scrolled_window);
 
   gtk_widget_class_bind_template_callback (widget_class, gtd_task_list_view__create_task);
   gtk_widget_class_bind_template_callback (widget_class, gtd_task_list_view__done_button_clicked);
@@ -1439,7 +1524,7 @@ gtd_task_list_view_init (GtdTaskListView *self)
                      0,
                      NULL,
                      0,
-                     GDK_ACTION_MOVE);
+                     GDK_ACTION_COPY);
 }
 
 /**


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]