[fractal] sidebar: Define subclass for item list



commit 35d3dd6d75b8d7603307793c7a4a3df6fa9c0d2c
Author: Kévin Commaille <zecakeh tedomum fr>
Date:   Mon Apr 11 18:34:22 2022 +0200

    sidebar: Define subclass for item list

 src/session/room/mod.rs                           |  10 +-
 src/session/sidebar/category.rs                   |  30 +++-
 src/session/sidebar/entry.rs                      |  14 +-
 src/session/sidebar/item_list.rs                  | 120 ++++------------
 src/session/sidebar/mod.rs                        |   7 +-
 src/session/sidebar/row.rs                        |  34 +++--
 src/session/sidebar/sidebar_item.rs               | 163 ++++++++++++++++++++++
 src/session/verification/identity_verification.rs |  12 +-
 8 files changed, 273 insertions(+), 117 deletions(-)
---
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index d918ec0a8..9688e5975 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -62,7 +62,10 @@ use crate::{
     gettext_f, ngettext_f,
     prelude::*,
     session::{
-        avatar::update_room_avatar_from_file, room::member_list::MemberList, Avatar, Session, User,
+        avatar::update_room_avatar_from_file,
+        room::member_list::MemberList,
+        sidebar::{SidebarItem, SidebarItemImpl},
+        Avatar, Session, User,
     },
     spawn, spawn_tokio,
     utils::pending_event_ids,
@@ -107,6 +110,7 @@ mod imp {
     impl ObjectSubclass for Room {
         const NAME: &'static str = "Room";
         type Type = super::Room;
+        type ParentType = SidebarItem;
     }
 
     impl ObjectImpl for Room {
@@ -335,13 +339,15 @@ mod imp {
             }
         }
     }
+
+    impl SidebarItemImpl for Room {}
 }
 
 glib::wrapper! {
     /// GObject representation of a Matrix room.
     ///
     /// Handles populating the Timeline.
-    pub struct Room(ObjectSubclass<imp::Room>);
+    pub struct Room(ObjectSubclass<imp::Room>) @extends SidebarItem;
 }
 
 impl Room {
diff --git a/src/session/sidebar/category.rs b/src/session/sidebar/category.rs
index 69dd90f23..6eecccdce 100644
--- a/src/session/sidebar/category.rs
+++ b/src/session/sidebar/category.rs
@@ -1,9 +1,13 @@
 use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
 
-use crate::session::{room::Room, room_list::RoomList, sidebar::CategoryType};
+use super::{CategoryType, SidebarItem, SidebarItemExt, SidebarItemImpl};
+use crate::session::{
+    room::{Room, RoomType},
+    room_list::RoomList,
+};
 
 mod imp {
-    use std::cell::Cell;
+    use std::{cell::Cell, convert::TryFrom};
 
     use once_cell::unsync::OnceCell;
 
@@ -20,6 +24,7 @@ mod imp {
     impl ObjectSubclass for Category {
         const NAME: &'static str = "Category";
         type Type = super::Category;
+        type ParentType = SidebarItem;
         type Interfaces = (gio::ListModel,);
     }
 
@@ -96,15 +101,33 @@ mod imp {
 
     impl ListModelImpl for Category {
         fn item_type(&self, _list_model: &Self::Type) -> glib::Type {
-            glib::Object::static_type()
+            SidebarItem::static_type()
         }
+
         fn n_items(&self, _list_model: &Self::Type) -> u32 {
             self.model.get().unwrap().n_items()
         }
+
         fn item(&self, _list_model: &Self::Type, position: u32) -> Option<glib::Object> {
             self.model.get().unwrap().item(position)
         }
     }
+
+    impl SidebarItemImpl for Category {
+        fn update_visibility(&self, obj: &Self::Type, for_category: CategoryType) {
+            obj.set_visible(
+                !obj.is_empty()
+                    || RoomType::try_from(for_category)
+                        .ok()
+                        .and_then(|room_type| {
+                            RoomType::try_from(obj.type_())
+                                .ok()
+                                .filter(|category| room_type.can_change_to(category))
+                        })
+                        .is_some(),
+            )
+        }
+    }
 }
 
 glib::wrapper! {
@@ -112,6 +135,7 @@ glib::wrapper! {
     ///
     /// This struct is used in ItemList for the sidebar.
     pub struct Category(ObjectSubclass<imp::Category>)
+        @extends SidebarItem,
         @implements gio::ListModel;
 }
 
diff --git a/src/session/sidebar/entry.rs b/src/session/sidebar/entry.rs
index 79c3ffbb0..c8f58dad3 100644
--- a/src/session/sidebar/entry.rs
+++ b/src/session/sidebar/entry.rs
@@ -1,6 +1,6 @@
 use gtk::{glib, prelude::*, subclass::prelude::*};
 
-use crate::session::sidebar::EntryType;
+use super::{CategoryType, EntryType, SidebarItem, SidebarItemExt, SidebarItemImpl};
 
 mod imp {
     use std::cell::{Cell, RefCell};
@@ -17,6 +17,7 @@ mod imp {
     impl ObjectSubclass for Entry {
         const NAME: &'static str = "Entry";
         type Type = super::Entry;
+        type ParentType = SidebarItem;
     }
 
     impl ObjectImpl for Entry {
@@ -79,6 +80,15 @@ mod imp {
             }
         }
     }
+
+    impl SidebarItemImpl for Entry {
+        fn update_visibility(&self, obj: &Self::Type, for_category: CategoryType) {
+            match obj.type_() {
+                EntryType::Explore => obj.set_visible(true),
+                EntryType::Forget => obj.set_visible(for_category == CategoryType::Left),
+            }
+        }
+    }
 }
 
 glib::wrapper! {
@@ -86,7 +96,7 @@ glib::wrapper! {
     ///
     /// Entry is supposed to be used in a TreeListModel, but as it does not have
     /// any children, implementing the ListModel interface is not required.
-    pub struct Entry(ObjectSubclass<imp::Entry>);
+    pub struct Entry(ObjectSubclass<imp::Entry>) @extends SidebarItem;
 }
 
 impl Entry {
diff --git a/src/session/sidebar/item_list.rs b/src/session/sidebar/item_list.rs
index bee93a346..5294d0713 100644
--- a/src/session/sidebar/item_list.rs
+++ b/src/session/sidebar/item_list.rs
@@ -1,13 +1,7 @@
-use std::convert::TryFrom;
-
 use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
 
-use crate::session::{
-    room::RoomType,
-    room_list::RoomList,
-    sidebar::{Category, CategoryType, Entry, EntryType},
-    verification::VerificationList,
-};
+use super::{Category, CategoryType, Entry, EntryType, SidebarItem, SidebarItemExt};
+use crate::session::{room_list::RoomList, verification::VerificationList};
 
 mod imp {
     use std::cell::Cell;
@@ -18,7 +12,7 @@ mod imp {
 
     #[derive(Debug, Default)]
     pub struct ItemList {
-        pub list: OnceCell<[(glib::Object, Cell<bool>); 8]>,
+        pub list: OnceCell<[SidebarItem; 8]>,
         pub room_list: OnceCell<RoomList>,
         pub verification_list: OnceCell<VerificationList>,
         /// The `CategoryType` to show all compatible categories for.
@@ -96,72 +90,48 @@ mod imp {
             let room_list = obj.room_list();
             let verification_list = obj.verification_list();
 
-            let list = [
-                Entry::new(EntryType::Explore).upcast::<glib::Object>(),
-                Category::new(CategoryType::VerificationRequest, verification_list)
-                    .upcast::<glib::Object>(),
-                Category::new(CategoryType::Invited, room_list).upcast::<glib::Object>(),
-                Category::new(CategoryType::Favorite, room_list).upcast::<glib::Object>(),
-                Category::new(CategoryType::Normal, room_list).upcast::<glib::Object>(),
-                Category::new(CategoryType::LowPriority, room_list).upcast::<glib::Object>(),
-                Category::new(CategoryType::Left, room_list).upcast::<glib::Object>(),
-                Entry::new(EntryType::Forget).upcast::<glib::Object>(),
+            let list: [SidebarItem; 8] = [
+                Entry::new(EntryType::Explore).upcast(),
+                Category::new(CategoryType::VerificationRequest, verification_list).upcast(),
+                Category::new(CategoryType::Invited, room_list).upcast(),
+                Category::new(CategoryType::Favorite, room_list).upcast(),
+                Category::new(CategoryType::Normal, room_list).upcast(),
+                Category::new(CategoryType::LowPriority, room_list).upcast(),
+                Category::new(CategoryType::Left, room_list).upcast(),
+                Entry::new(EntryType::Forget).upcast(),
             ];
 
-            for (index, item) in list.iter().enumerate() {
+            for item in list.iter() {
                 if let Some(category) = item.downcast_ref::<Category>() {
                     category.connect_notify_local(
                         Some("empty"),
-                        clone!(@weak obj => move |_, _| {
-                            obj.update_item(index);
+                        clone!(@weak obj => move |category, _| {
+                            category.update_visibility(obj.show_all_for_category());
                         }),
                     );
                 }
+                item.update_visibility(obj.show_all_for_category());
             }
 
-            let list = list.map(|item| {
-                let visible = if let Some(category) = item.downcast_ref::<Category>() {
-                    !category.is_empty()
-                } else {
-                    item.downcast_ref::<Entry>()
-                        .filter(|entry| entry.type_() == EntryType::Forget)
-                        .is_none()
-                };
-                (item, Cell::new(visible))
-            });
-
             self.list.set(list).unwrap();
         }
     }
 
     impl ListModelImpl for ItemList {
         fn item_type(&self, _list_model: &Self::Type) -> glib::Type {
-            glib::Object::static_type()
+            SidebarItem::static_type()
         }
+
         fn n_items(&self, _list_model: &Self::Type) -> u32 {
-            self.list
-                .get()
-                .unwrap()
-                .iter()
-                .filter(|(_, visible)| visible.get())
-                .count() as u32
+            self.list.get().unwrap().len() as u32
         }
+
         fn item(&self, _list_model: &Self::Type, position: u32) -> Option<glib::Object> {
             self.list
                 .get()
                 .unwrap()
-                .iter()
-                .filter_map(
-                    |(item, visible)| {
-                        if visible.get() {
-                            Some(item)
-                        } else {
-                            None
-                        }
-                    },
-                )
-                .nth(position as usize)
-                .cloned()
+                .get(position as usize)
+                .map(|item| item.to_owned().upcast())
         }
     }
 }
@@ -184,48 +154,6 @@ impl ItemList {
         .expect("Failed to create ItemList")
     }
 
-    fn update_item(&self, position: usize) {
-        let priv_ = self.imp();
-        let (item, old_visible) = priv_.list.get().unwrap().get(position).unwrap();
-
-        let visible = if let Some(category) = item.downcast_ref::<Category>() {
-            !category.is_empty()
-                || RoomType::try_from(self.show_all_for_category())
-                    .ok()
-                    .and_then(|room_type| {
-                        RoomType::try_from(category.type_())
-                            .ok()
-                            .filter(|category| room_type.can_change_to(category))
-                    })
-                    .is_some()
-        } else if item
-            .downcast_ref::<Entry>()
-            .filter(|entry| entry.type_() == EntryType::Forget)
-            .is_some()
-        {
-            self.show_all_for_category() == CategoryType::Left
-        } else {
-            return;
-        };
-
-        if visible != old_visible.get() {
-            old_visible.set(visible);
-            let hidden_before_position = priv_
-                .list
-                .get()
-                .unwrap()
-                .iter()
-                .take(position)
-                .filter(|(_, visible)| !visible.get())
-                .count();
-            let real_position = position - hidden_before_position;
-
-            let (removed, added) = if visible { (0, 1) } else { (1, 0) };
-
-            self.items_changed(real_position as u32, removed, added);
-        }
-    }
-
     pub fn show_all_for_category(&self) -> CategoryType {
         self.imp().show_all_for_category.get()
     }
@@ -238,8 +166,8 @@ impl ItemList {
         }
 
         priv_.show_all_for_category.set(category);
-        for i in 0..priv_.list.get().unwrap().len() {
-            self.update_item(i);
+        for item in priv_.list.get().unwrap().iter() {
+            item.update_visibility(category);
         }
 
         self.notify("show-all-for-category");
diff --git a/src/session/sidebar/mod.rs b/src/session/sidebar/mod.rs
index c43aa8176..5ffb35839 100644
--- a/src/session/sidebar/mod.rs
+++ b/src/session/sidebar/mod.rs
@@ -9,6 +9,7 @@ mod item_list;
 mod room_row;
 mod row;
 mod selection;
+mod sidebar_item;
 mod verification_row;
 
 use account_switcher::AccountSwitcher;
@@ -16,8 +17,12 @@ use adw::{prelude::*, subclass::prelude::*};
 use gtk::{gio, glib, glib::closure, subclass::prelude::*, CompositeTemplate, SelectionModel};
 
 pub use self::{
-    category::Category, category_type::CategoryType, entry::Entry, entry_type::EntryType,
+    category::Category,
+    category_type::CategoryType,
+    entry::Entry,
+    entry_type::EntryType,
     item_list::ItemList,
+    sidebar_item::{SidebarItem, SidebarItemExt, SidebarItemImpl},
 };
 use self::{
     category_row::CategoryRow, entry_row::EntryRow, room_row::RoomRow, row::Row,
diff --git a/src/session/sidebar/row.rs b/src/session/sidebar/row.rs
index 0a17f6e45..eda20b99f 100644
--- a/src/session/sidebar/row.rs
+++ b/src/session/sidebar/row.rs
@@ -6,7 +6,7 @@ use gtk::{gdk, glib, glib::clone, subclass::prelude::*};
 use super::EntryType;
 use crate::session::{
     room::{Room, RoomType},
-    sidebar::{Category, CategoryRow, Entry, EntryRow, RoomRow, VerificationRow},
+    sidebar::{Category, CategoryRow, Entry, EntryRow, RoomRow, SidebarItem, VerificationRow},
     verification::IdentityVerification,
 };
 
@@ -20,7 +20,7 @@ mod imp {
     #[derive(Debug, Default)]
     pub struct Row {
         pub list_row: RefCell<Option<gtk::TreeListRow>>,
-        pub binding: RefCell<Option<glib::Binding>>,
+        pub bindings: RefCell<Vec<glib::Binding>>,
     }
 
     #[glib::object_subclass]
@@ -119,8 +119,10 @@ impl Row {
         glib::Object::new(&[]).expect("Failed to create Row")
     }
 
-    pub fn item(&self) -> Option<glib::Object> {
-        self.list_row().and_then(|r| r.item())
+    pub fn item(&self) -> Option<SidebarItem> {
+        self.list_row()
+            .and_then(|r| r.item())
+            .and_then(|obj| obj.downcast().ok())
     }
 
     pub fn list_row(&self) -> Option<gtk::TreeListRow> {
@@ -134,7 +136,7 @@ impl Row {
             return;
         }
 
-        if let Some(binding) = priv_.binding.take() {
+        for binding in priv_.bindings.take() {
             binding.unbind();
         }
 
@@ -145,7 +147,16 @@ impl Row {
             return;
         };
 
+        let mut bindings = vec![];
         if let Some(item) = self.item() {
+            if let Some(list_item) = self.parent() {
+                bindings.push(
+                    item.bind_property("visible", &list_item, "visible")
+                        .flags(glib::BindingFlags::SYNC_CREATE)
+                        .build(),
+                );
+            }
+
             if let Some(category) = item.downcast_ref::<Category>() {
                 let child =
                     if let Some(Ok(child)) = self.child().map(|w| w.downcast::<CategoryRow>()) {
@@ -157,12 +168,11 @@ impl Row {
                     };
                 child.set_category(Some(category.clone()));
 
-                let binding = row
-                    .bind_property("expanded", &child, "expanded")
-                    .flags(glib::BindingFlags::SYNC_CREATE)
-                    .build();
-
-                priv_.binding.replace(Some(binding));
+                bindings.push(
+                    row.bind_property("expanded", &child, "expanded")
+                        .flags(glib::BindingFlags::SYNC_CREATE)
+                        .build(),
+                );
 
                 if let Some(list_item) = self.parent() {
                     list_item.set_css_classes(&["category"]);
@@ -223,6 +233,8 @@ impl Row {
                 .unwrap();
         }
 
+        priv_.bindings.replace(bindings);
+
         self.notify("item");
         self.notify("list-row");
     }
diff --git a/src/session/sidebar/sidebar_item.rs b/src/session/sidebar/sidebar_item.rs
new file mode 100644
index 000000000..339ed6632
--- /dev/null
+++ b/src/session/sidebar/sidebar_item.rs
@@ -0,0 +1,163 @@
+use gtk::{glib, prelude::*, subclass::prelude::*};
+
+use super::CategoryType;
+
+mod imp {
+    use std::cell::Cell;
+
+    use once_cell::sync::Lazy;
+
+    use super::*;
+
+    #[repr(C)]
+    pub struct SidebarItemClass {
+        pub parent_class: glib::object::ObjectClass,
+        pub update_visibility: fn(&super::SidebarItem, for_category: CategoryType),
+    }
+
+    unsafe impl ClassStruct for SidebarItemClass {
+        type Type = SidebarItem;
+    }
+
+    pub(super) fn sidebar_item_update_visibility(
+        this: &super::SidebarItem,
+        for_category: CategoryType,
+    ) {
+        let klass = this.class();
+        (klass.as_ref().update_visibility)(this, for_category)
+    }
+
+    #[derive(Debug)]
+    pub struct SidebarItem {
+        /// Whether this item is visible.
+        pub visible: Cell<bool>,
+    }
+
+    impl Default for SidebarItem {
+        fn default() -> Self {
+            Self {
+                visible: Cell::new(true),
+            }
+        }
+    }
+
+    #[glib::object_subclass]
+    unsafe impl ObjectSubclass for SidebarItem {
+        const NAME: &'static str = "SidebarItem";
+        const ABSTRACT: bool = true;
+        type Type = super::SidebarItem;
+        type Class = SidebarItemClass;
+    }
+
+    impl ObjectImpl for SidebarItem {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![glib::ParamSpecBoolean::new(
+                    "visible",
+                    "Visible",
+                    "Whether this item is visible.",
+                    true,
+                    glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                )]
+            });
+
+            PROPERTIES.as_ref()
+        }
+
+        fn set_property(
+            &self,
+            obj: &Self::Type,
+            _id: usize,
+            value: &glib::Value,
+            pspec: &glib::ParamSpec,
+        ) {
+            match pspec.name() {
+                "visible" => obj.set_visible(value.get().unwrap()),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "visible" => obj.visible().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+    }
+}
+
+glib::wrapper! {
+    /// Parent class of items inside the `Sidebar`.
+    pub struct SidebarItem(ObjectSubclass<imp::SidebarItem>);
+}
+
+/// Public trait containing implemented methods for everything that derives from
+/// `SidebarItem`.
+///
+/// To override the behavior of these methods, override the corresponding method
+/// of `SidebarItemImpl`.
+pub trait SidebarItemExt: 'static {
+    /// Whether this `SidebarItem` is visible.
+    ///
+    /// Defaults to `true`.
+    fn visible(&self) -> bool;
+
+    /// Set the visibility of the `SidebarItem`.
+    fn set_visible(&self, visible: bool);
+
+    /// Update the visibility of the `SidebarItem` for the given `CategoryType`.
+    fn update_visibility(&self, for_category: CategoryType);
+}
+
+impl<O: IsA<SidebarItem>> SidebarItemExt for O {
+    fn visible(&self) -> bool {
+        self.upcast_ref().imp().visible.get()
+    }
+
+    fn set_visible(&self, visible: bool) {
+        if self.visible() == visible {
+            return;
+        }
+
+        self.upcast_ref().imp().visible.set(visible);
+        self.notify("visible");
+    }
+
+    fn update_visibility(&self, for_category: CategoryType) {
+        imp::sidebar_item_update_visibility(self.upcast_ref(), for_category)
+    }
+}
+
+/// Public trait that must be implemented for everything that derives from
+/// `SidebarItem`.
+///
+/// Overriding a method from this Trait overrides also its behavior in
+/// `SidebarItemExt`.
+pub trait SidebarItemImpl: ObjectImpl {
+    fn update_visibility(&self, _obj: &Self::Type, _for_category: CategoryType) {}
+}
+
+// Make `SidebarItem` subclassable.
+unsafe impl<T> IsSubclassable<T> for SidebarItem
+where
+    T: SidebarItemImpl,
+    T::Type: IsA<SidebarItem>,
+{
+    fn class_init(class: &mut glib::Class<Self>) {
+        Self::parent_class_init::<T>(class.upcast_ref_mut());
+
+        let klass = class.as_mut();
+
+        klass.update_visibility = update_visibility_trampoline::<T>;
+    }
+}
+
+// Virtual method implementation trampolines.
+fn update_visibility_trampoline<T>(this: &SidebarItem, for_category: CategoryType)
+where
+    T: ObjectSubclass + SidebarItemImpl,
+    T::Type: IsA<SidebarItem>,
+{
+    let this = this.downcast_ref::<T::Type>().unwrap();
+    this.imp().update_visibility(this, for_category)
+}
diff --git a/src/session/verification/identity_verification.rs 
b/src/session/verification/identity_verification.rs
index e7082e909..b0281efc5 100644
--- a/src/session/verification/identity_verification.rs
+++ b/src/session/verification/identity_verification.rs
@@ -24,7 +24,11 @@ use super::{VERIFICATION_CREATION_TIMEOUT, VERIFICATION_RECEIVE_TIMEOUT};
 use crate::{
     components::Toast,
     contrib::Camera,
-    session::{user::UserExt, Session, User},
+    session::{
+        sidebar::{SidebarItem, SidebarItemImpl},
+        user::UserExt,
+        Session, User,
+    },
     spawn, spawn_tokio,
 };
 
@@ -188,6 +192,7 @@ mod imp {
     impl ObjectSubclass for IdentityVerification {
         const NAME: &'static str = "IdentityVerification";
         type Type = super::IdentityVerification;
+        type ParentType = SidebarItem;
     }
 
     impl ObjectImpl for IdentityVerification {
@@ -354,10 +359,13 @@ mod imp {
             obj.cancel(true);
         }
     }
+
+    impl SidebarItemImpl for IdentityVerification {}
 }
 
 glib::wrapper! {
-    pub struct IdentityVerification(ObjectSubclass<imp::IdentityVerification>);
+    pub struct IdentityVerification(ObjectSubclass<imp::IdentityVerification>)
+        @extends SidebarItem;
 }
 
 impl IdentityVerification {


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