[gnome-todo] task-list-view: Reintroduce empty state



commit 45709723cb9d5ae3dbf9ac690933e63764036b3d
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Tue Apr 13 11:51:56 2021 -0300

    task-list-view: Reintroduce empty state
    
    Now redesigned with Jakub's new artwork. GtdTaskListView requires a new
    filter list to figure if the empty state should be about "no more tasks
    remaining" or "no tasks whatsoever" cases.

 src/gui/gtd-task-list-view.c  | 104 ++++++++++++++++++++++++++++-
 src/gui/gtd-task-list-view.ui | 148 ++++++++++++++++++++++++++----------------
 2 files changed, 193 insertions(+), 59 deletions(-)
---
diff --git a/src/gui/gtd-task-list-view.c b/src/gui/gtd-task-list-view.c
index bf89e022..19f7da6f 100644
--- a/src/gui/gtd-task-list-view.c
+++ b/src/gui/gtd-task-list-view.c
@@ -69,7 +69,9 @@
 
 typedef struct
 {
+  GtdEmptyListWidget    *empty_list_widget;
   GtkListBox            *listbox;
+  GtkStack              *main_stack;
   GtkListBoxRow         *new_task_row;
   GtkWidget             *scrolled_window;
   GtkStack              *stack;
@@ -82,6 +84,9 @@ typedef struct
   GListModel            *model;
   GDateTime             *default_date;
 
+  GListModel            *incomplete_tasks_model;
+  guint                  n_incomplete_tasks;
+
   guint                  scroll_to_bottom_handler_id;
 
   GHashTable            *task_to_row;
@@ -142,10 +147,19 @@ typedef struct
 #define TASK_REMOVED_NOTIFICATION_ID "task-removed-id"
 
 
+static gboolean      filter_complete_func                        (gpointer            item,
+                                                                  gpointer            user_data);
+
 static void          on_clear_completed_tasks_activated_cb       (GSimpleAction      *simple,
                                                                   GVariant           *parameter,
                                                                   gpointer            user_data);
 
+static void          on_incomplete_tasks_items_changed_cb        (GListModel         *model,
+                                                                  guint               position,
+                                                                  guint               n_removed,
+                                                                  guint               n_added,
+                                                                  GtdTaskListView    *self);
+
 static void          on_remove_task_row_cb                       (GtdTaskRow         *row,
                                                                   GtdTaskListView    *self);
 
@@ -276,11 +290,88 @@ schedule_scroll_to_bottom (GtdTaskListView *self)
   priv->scroll_to_bottom_handler_id = g_timeout_add (250, scroll_to_bottom_cb, self);
 }
 
+static void
+update_empty_state (GtdTaskListView *self)
+{
+  GtdTaskListViewPrivate *priv = gtd_task_list_view_get_instance_private (self);
+  gboolean show_empty_list_widget;
+  gboolean is_empty;
+
+  g_assert (GTD_IS_TASK_LIST_VIEW (self));
+
+  is_empty = g_list_model_get_n_items (priv->model) == 0;
+  gtd_empty_list_widget_set_is_empty (priv->empty_list_widget, is_empty);
+
+  show_empty_list_widget = !GTD_IS_TASK_LIST (priv->model) &&
+                           (is_empty || priv->n_incomplete_tasks == 0);
+  gtk_stack_set_visible_child_name (priv->main_stack,
+                                    show_empty_list_widget ? "empty-list" : "task-list");
+}
+
+static void
+update_incomplete_tasks_model (GtdTaskListView *self)
+{
+  GtdTaskListViewPrivate *priv = gtd_task_list_view_get_instance_private (self);
+
+  if (!priv->incomplete_tasks_model)
+    {
+      g_autoptr (GtkFilterListModel) filter_model = NULL;
+      GtkCustomFilter *filter;
+
+      filter = gtk_custom_filter_new (filter_complete_func, self, NULL);
+      filter_model = gtk_filter_list_model_new (NULL, GTK_FILTER (filter));
+      gtk_filter_list_model_set_incremental (filter_model, TRUE);
+
+      priv->incomplete_tasks_model = G_LIST_MODEL (g_steal_pointer (&filter_model));
+    }
+
+  gtk_filter_list_model_set_model (GTK_FILTER_LIST_MODEL (priv->incomplete_tasks_model),
+                                   priv->model);
+  priv->n_incomplete_tasks = g_list_model_get_n_items (priv->incomplete_tasks_model);
+
+  g_signal_connect (priv->incomplete_tasks_model,
+                    "items-changed",
+                    G_CALLBACK (on_incomplete_tasks_items_changed_cb),
+                    self);
+}
+
 
 /*
  * Callbacks
  */
 
+static gboolean
+filter_complete_func (gpointer item,
+                      gpointer user_data)
+{
+  GtdTask *task = (GtdTask*) item;
+  return !gtd_task_get_complete (task);
+}
+
+static void
+on_incomplete_tasks_items_changed_cb (GListModel      *model,
+                                      guint            position,
+                                      guint            n_removed,
+                                      guint            n_added,
+                                      GtdTaskListView *self)
+{
+  GtdTaskListViewPrivate *priv = gtd_task_list_view_get_instance_private (self);
+
+  priv->n_incomplete_tasks -= n_removed;
+  priv->n_incomplete_tasks += n_added;
+
+  update_empty_state (self);
+}
+
+static void
+on_empty_list_widget_add_tasks_cb (GtdEmptyListWidget *empty_list_widget,
+                                   GtdTaskListView    *self)
+{
+  GtdTaskListViewPrivate *priv = gtd_task_list_view_get_instance_private (self);
+
+  gtk_stack_set_visible_child_name (priv->main_stack, "task-list");
+}
+
 static GtkWidget*
 create_row_for_task_cb (gpointer item,
                         gpointer user_data)
@@ -944,6 +1035,7 @@ gtd_task_list_view_finalize (GObject *object)
   g_clear_handle_id (&priv->scroll_to_bottom_handler_id, g_source_remove);
   g_clear_pointer (&priv->task_to_row, g_hash_table_destroy);
   g_clear_pointer (&priv->default_date, g_date_time_unref);
+  g_clear_object (&priv->incomplete_tasks_model);
   g_clear_object (&priv->renderer);
   g_clear_object (&priv->model);
 
@@ -1051,12 +1143,15 @@ gtd_task_list_view_constructed (GObject *object)
 static void
 gtd_task_list_view_map (GtkWidget *widget)
 {
-  GtdTaskListViewPrivate *priv;
+  GtdTaskListView *self = GTD_TASK_LIST_VIEW (widget);
+  GtdTaskListViewPrivate *priv = gtd_task_list_view_get_instance_private (self);
   GtkRoot *root;
 
+
+  update_empty_state (self);
+
   GTK_WIDGET_CLASS (gtd_task_list_view_parent_class)->map (widget);
 
-  priv = GTD_TASK_LIST_VIEW (widget)->priv;
   root = gtk_widget_get_root (widget);
 
   /* Clear previously added "list" actions */
@@ -1159,12 +1254,15 @@ gtd_task_list_view_class_init (GtdTaskListViewClass *klass)
   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/todo/ui/gtd-task-list-view.ui");
 
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, due_date_sizegroup);
+  gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, empty_list_widget);
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, listbox);
+  gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, main_stack);
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, new_task_row);
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, tasklist_name_sizegroup);
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, scrolled_window);
   gtk_widget_class_bind_template_child_private (widget_class, GtdTaskListView, stack);
 
+  gtk_widget_class_bind_template_callback (widget_class, on_empty_list_widget_add_tasks_cb);
   gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_activated_cb);
   gtk_widget_class_bind_template_callback (widget_class, on_new_task_row_entered_cb);
   gtk_widget_class_bind_template_callback (widget_class, on_new_task_row_exited_cb);
@@ -1300,6 +1398,8 @@ gtd_task_list_view_set_model (GtdTaskListView *view,
                            NULL);
 
   schedule_scroll_to_bottom (view);
+  update_incomplete_tasks_model (view);
+  update_empty_state (view);
 
   if (priv->task_list_selector_behavior == GTD_TASK_LIST_SELECTOR_BEHAVIOR_AUTOMATIC)
     gtd_new_task_row_set_show_list_selector (GTD_NEW_TASK_ROW (priv->new_task_row), !GTD_IS_TASK_LIST 
(model));
diff --git a/src/gui/gtd-task-list-view.ui b/src/gui/gtd-task-list-view.ui
index 9a0775dc..1682bf95 100644
--- a/src/gui/gtd-task-list-view.ui
+++ b/src/gui/gtd-task-list-view.ui
@@ -3,74 +3,108 @@
   <template class="GtdTaskListView" parent="GtkBox">
     <property name="vexpand">1</property>
     <property name="orientation">vertical</property>
+
+    <!-- Main stack -->
     <child>
-      <object class="GtkScrolledWindow" id="scrolled_window">
-        <property name="can_focus">1</property>
-        <property name="hexpand">1</property>
-        <property name="vexpand">1</property>
-        <property name="min-content-height">320</property>
-        <property name="hscrollbar-policy">never</property>
+      <object class="GtkStack" id="main_stack">
+        <property name="hexpand">true</property>
+        <property name="vexpand">true</property>
+        <property name="transition-type">crossfade</property>
+
+        <!-- Task list page -->
         <child>
-          <object class="GtkStack" id="stack">
-            <property name="hexpand">true</property>
-            <property name="vexpand">true</property>
-            <property name="transition-type">crossfade</property>
-            <child>
-              <object class="GtkStackPage">
-                <property name="name">listbox</property>
-                <property name="child">
-                  <object class="GtdWidget">
-                    <property name="hexpand">1</property>
-                    <property name="vexpand">1</property>
-                    <property name="halign">center</property>
-                    <property name="layout-manager">
-                      <object class="GtdMaxSizeLayout">
-                        <property name="max-width">700</property>
-                      </object>
-                    </property>
+          <object class="GtkStackPage">
+            <property name="name">task-list</property>
+            <property name="child">
+              <object class="GtkScrolledWindow" id="scrolled_window">
+                <property name="can_focus">1</property>
+                <property name="hexpand">1</property>
+                <property name="vexpand">1</property>
+                <property name="min-content-height">320</property>
+                <property name="hscrollbar-policy">never</property>
+                <child>
+                  <object class="GtkStack" id="stack">
+                    <property name="hexpand">true</property>
+                    <property name="vexpand">true</property>
+                    <property name="transition-type">crossfade</property>
                     <child>
-                      <object class="GtkBox">
-                        <property name="margin-top">6</property>
-                        <property name="margin-bottom">64</property>
-                        <property name="margin-start">18</property>
-                        <property name="margin-end">18</property>
-                        <property name="orientation">vertical</property>
-                        <child>
-                          <object class="GtkListBox" id="listbox">
+                      <object class="GtkStackPage">
+                        <property name="name">listbox</property>
+                        <property name="child">
+                          <object class="GtdWidget">
                             <property name="hexpand">1</property>
-                            <property name="selection_mode">none</property>
-                            <signal name="row-activated" handler="on_listbox_row_activated_cb" 
object="GtdTaskListView" swapped="no"/>
-                            <style>
-                              <class name="transparent"/>
-                            </style>
+                            <property name="vexpand">1</property>
+                            <property name="halign">center</property>
+                            <property name="layout-manager">
+                              <object class="GtdMaxSizeLayout">
+                                <property name="max-width">700</property>
+                              </object>
+                            </property>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="margin-top">6</property>
+                                <property name="margin-bottom">64</property>
+                                <property name="margin-start">18</property>
+                                <property name="margin-end">18</property>
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkListBox" id="listbox">
+                                    <property name="hexpand">1</property>
+                                    <property name="selection_mode">none</property>
+                                    <signal name="row-activated" handler="on_listbox_row_activated_cb" 
object="GtdTaskListView" swapped="no"/>
+                                    <style>
+                                      <class name="transparent"/>
+                                    </style>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtdNewTaskRow" id="new_task_row">
+                                    <property name="margin-bottom">24</property>
+                                    <signal name="enter" handler="on_new_task_row_entered_cb" 
object="GtdTaskListView" swapped="yes"/>
+                                    <signal name="exit" handler="on_new_task_row_exited_cb" 
object="GtdTaskListView" swapped="yes"/>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
                           </object>
-                        </child>
-                        <child>
-                          <object class="GtdNewTaskRow" id="new_task_row">
-                            <property name="margin-bottom">24</property>
-                            <signal name="enter" handler="on_new_task_row_entered_cb" 
object="GtdTaskListView" swapped="yes"/>
-                            <signal name="exit" handler="on_new_task_row_exited_cb" object="GtdTaskListView" 
swapped="yes"/>
+
+                        </property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkStackPage">
+                        <property name="name">loading</property>
+                        <property name="child">
+                          <object class="GtkSpinner">
+                            <property name="spinning">true</property>
+                            <property name="width-request">96</property>
+                            <property name="height-request">96</property>
                           </object>
-                        </child>
+                        </property>
                       </object>
                     </child>
                   </object>
-
-                </property>
+                </child>
               </object>
-            </child>
-            <child>
-              <object class="GtkStackPage">
-                <property name="name">loading</property>
-                <property name="child">
-                  <object class="GtkSpinner">
-                    <property name="spinning">true</property>
-                    <property name="width-request">96</property>
-                    <property name="height-request">96</property>
-                  </object>
-                </property>
+
+            </property>
+          </object>
+        </child>
+
+        <!-- Empty list widget -->
+        <child>
+          <object class="GtkStackPage">
+            <property name="name">empty-list</property>
+            <property name="child">
+              <object class="GtdEmptyListWidget" id="empty_list_widget">
+                <property name="halign">center</property>
+                <property name="valign">center</property>
+                <signal name="add-tasks" handler="on_empty_list_widget_add_tasks_cb" 
object="GtdTaskListView" swapped="no" />
+                <layout>
+                  <property name="measure">true</property>
+                </layout>
               </object>
-            </child>
+            </property>
           </object>
         </child>
 


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