From f0f0c4cbb9c43a26b0f3b32810a56f885dc6e46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= <46488-zecakeh1@users.noreply.gitlab.gnome.org> Date: Wed, 19 May 2021 06:24:29 +0000 Subject: [PATCH] session: Use a single room list with GtkFilterListModels --- Cargo.lock | 1 + Cargo.toml | 1 + src/session/categories/categories.rs | 74 +++------------ src/session/categories/category.rs | 96 +++++++++---------- src/session/categories/mod.rs | 2 + src/session/categories/room_list.rs | 132 +++++++++++++++++++++++++++ src/session/mod.rs | 25 ++--- 7 files changed, 201 insertions(+), 130 deletions(-) create mode 100644 src/session/categories/room_list.rs diff --git a/Cargo.lock b/Cargo.lock index 5ed72023..45323381 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -678,6 +678,7 @@ dependencies = [ "gtk-macros", "gtk4", "html2pango", + "indexmap", "libadwaita", "log", "matrix-sdk", diff --git a/Cargo.toml b/Cargo.toml index 8bbb11b0..614dd516 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ html2pango = "0.4" futures = "0.3" comrak = "0.10" rand = "0.8" +indexmap = "1.6.2" [dependencies.sourceview] branch = "main" diff --git a/src/session/categories/categories.rs b/src/session/categories/categories.rs index 687d67e1..6ef7480f 100644 --- a/src/session/categories/categories.rs +++ b/src/session/categories/categories.rs @@ -1,19 +1,14 @@ -use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*}; -use std::collections::HashMap; +use gtk::{gio, glib, prelude::*, subclass::prelude::*}; -use crate::session::{ - categories::{Category, CategoryType}, - room::Room, -}; +use crate::session::categories::{Category, CategoryType, RoomList}; mod imp { use super::*; - use std::cell::RefCell; #[derive(Debug)] pub struct Categories { pub list: [Category; 5], - pub room_map: RefCell>, + pub room_list: RoomList, } #[glib::object_subclass] @@ -24,15 +19,17 @@ mod imp { type Interfaces = (gio::ListModel,); fn new() -> Self { + let room_list = RoomList::new(); + Self { list: [ - Category::new(CategoryType::Invited), - Category::new(CategoryType::Favorite), - Category::new(CategoryType::Normal), - Category::new(CategoryType::LowPriority), - Category::new(CategoryType::Left), + Category::new(CategoryType::Invited, &room_list), + Category::new(CategoryType::Favorite, &room_list), + Category::new(CategoryType::Normal, &room_list), + Category::new(CategoryType::LowPriority, &room_list), + Category::new(CategoryType::Left, &room_list), ], - room_map: Default::default(), + room_list, } } } @@ -71,53 +68,8 @@ impl Categories { glib::Object::new(&[]).expect("Failed to create Categories") } - pub fn append(&self, rooms: Vec) { + pub fn room_list(&self) -> &RoomList { let priv_ = imp::Categories::from_instance(&self); - - let rooms: Vec = { - let room_map = priv_.room_map.borrow(); - rooms - .into_iter() - .filter(|room| !room_map.contains_key(&room)) - .collect() - }; - - let rooms_by_category = rooms.into_iter().fold(HashMap::new(), |mut acc, room| { - acc.entry(room.category()).or_insert(vec![]).push(room); - acc - }); - let mut room_map = priv_.room_map.borrow_mut(); - for (category_type, rooms) in rooms_by_category { - for room in &rooms { - room_map.insert(room.clone(), category_type); - room.connect_notify_local( - Some("category"), - clone!(@weak self as obj => move |room, _| { - obj.move_room(room); - }), - ); - } - - self.find_category_by_type(category_type) - .append_batch(rooms); - } - } - - fn find_category_by_type(&self, type_: CategoryType) -> &Category { - let priv_ = imp::Categories::from_instance(&self); - let position = priv_.list.iter().position(|item| item.type_() == type_); - priv_.list.get(position.unwrap()).unwrap() - } - - fn move_room(&self, room: &Room) { - let priv_ = imp::Categories::from_instance(&self); - let mut room_map = priv_.room_map.borrow_mut(); - - if let Some(old_category_type) = room_map.remove(&room) { - self.find_category_by_type(old_category_type).remove(room); - } - - room_map.insert(room.clone(), room.category()); - self.find_category_by_type(room.category()).append(room); + &priv_.room_list } } diff --git a/src/session/categories/category.rs b/src/session/categories/category.rs index 08823535..369d4c10 100644 --- a/src/session/categories/category.rs +++ b/src/session/categories/category.rs @@ -1,14 +1,19 @@ -use gtk::{gio, glib, prelude::*, subclass::prelude::*}; +use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*}; -use crate::session::{categories::CategoryType, room::Room}; +use crate::session::{ + categories::{CategoryType, RoomList}, + room::Room, +}; mod imp { + use once_cell::unsync::OnceCell; + use std::cell::Cell; + use super::*; - use std::cell::{Cell, RefCell}; #[derive(Debug, Default)] pub struct Category { - pub list: RefCell>, + pub model: OnceCell, pub type_: Cell, } @@ -40,6 +45,13 @@ mod imp { None, glib::ParamFlags::READABLE, ), + glib::ParamSpec::new_object( + "model", + "Model", + "The filter list model in that category", + gio::ListModel::static_type(), + glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY, + ), ] }); @@ -48,7 +60,7 @@ mod imp { fn set_property( &self, - _obj: &Self::Type, + obj: &Self::Type, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec, @@ -58,6 +70,10 @@ mod imp { let type_ = value.get().unwrap(); self.type_.set(type_); } + "model" => { + let model = value.get().unwrap(); + obj.set_model(model); + } _ => unimplemented!(), } } @@ -66,6 +82,7 @@ mod imp { match pspec.name() { "type" => obj.type_().to_value(), "display-name" => obj.type_().to_string().to_value(), + "model" => self.model.get().to_value(), _ => unimplemented!(), } } @@ -76,13 +93,10 @@ mod imp { Room::static_type() } fn n_items(&self, _list_model: &Self::Type) -> u32 { - self.list.borrow().len() as u32 + self.model.get().map(|l| l.n_items()).unwrap_or(0) } fn item(&self, _list_model: &Self::Type, position: u32) -> Option { - self.list - .borrow() - .get(position as usize) - .map(|o| o.clone().upcast::()) + self.model.get().and_then(|l| l.item(position)) } } } @@ -93,8 +107,8 @@ glib::wrapper! { } impl Category { - pub fn new(type_: CategoryType) -> Self { - glib::Object::new(&[("type", &type_)]).expect("Failed to create Category") + pub fn new(type_: CategoryType, model: &RoomList) -> Self { + glib::Object::new(&[("type", &type_), ("model", model)]).expect("Failed to create Category") } pub fn type_(&self) -> CategoryType { @@ -102,47 +116,23 @@ impl Category { priv_.type_.get() } - pub fn append(&self, room: &Room) { - let priv_ = imp::Category::from_instance(self); - let index = { - let mut list = priv_.list.borrow_mut(); - let index = list.len(); - list.push(room.clone()); - index - }; - self.items_changed(index as u32, 0, 1); - } - - pub fn append_batch(&self, rooms: Vec) { - let priv_ = imp::Category::from_instance(self); - let added = rooms.len(); - let index = { - let mut list = priv_.list.borrow_mut(); - let index = list.len(); - list.reserve(added); - for room in rooms { - list.push(room); - } - index - }; - self.items_changed(index as u32, 0, added as u32); - } - - pub fn remove(&self, room: &Room) { + fn set_model(&self, model: gio::ListModel) { let priv_ = imp::Category::from_instance(self); - - let index = { - let mut list = priv_.list.borrow_mut(); - - let index = list.iter().position(|item| item == room); - if let Some(index) = index { - list.remove(index); - } - index - }; - - if let Some(index) = index { - self.items_changed(index as u32, 1, 0); - } + let type_ = self.type_(); + + let filter = gtk::CustomFilter::new(move |o| { + o.downcast_ref::() + .filter(|r| r.category() == type_) + .is_some() + }); + let filter_model = gtk::FilterListModel::new(Some(&model), Some(&filter)); + + filter_model.connect_items_changed( + clone!(@weak self as obj => move |_, pos, added, removed| { + obj.items_changed(pos, added, removed); + }), + ); + + let _ = priv_.model.set(filter_model); } } diff --git a/src/session/categories/mod.rs b/src/session/categories/mod.rs index 9b644b47..f58f4f0a 100644 --- a/src/session/categories/mod.rs +++ b/src/session/categories/mod.rs @@ -1,7 +1,9 @@ mod categories; mod category; mod category_type; +mod room_list; pub use self::categories::Categories; pub use self::category::Category; pub use self::category_type::CategoryType; +pub use self::room_list::RoomList; diff --git a/src/session/categories/room_list.rs b/src/session/categories/room_list.rs new file mode 100644 index 00000000..7b5056b5 --- /dev/null +++ b/src/session/categories/room_list.rs @@ -0,0 +1,132 @@ +use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*}; +use indexmap::map::IndexMap; +use matrix_sdk::identifiers::RoomId; + +use crate::session::room::Room; + +mod imp { + use super::*; + use std::cell::RefCell; + + #[derive(Debug)] + pub struct RoomList { + pub list: RefCell>, + } + + #[glib::object_subclass] + impl ObjectSubclass for RoomList { + const NAME: &'static str = "RoomList"; + type Type = super::RoomList; + type ParentType = glib::Object; + type Interfaces = (gio::ListModel,); + + fn new() -> Self { + Self { + list: Default::default(), + } + } + } + + impl ObjectImpl for RoomList {} + + impl ListModelImpl for RoomList { + fn item_type(&self, _list_model: &Self::Type) -> glib::Type { + Room::static_type() + } + fn n_items(&self, _list_model: &Self::Type) -> u32 { + self.list.borrow().len() as u32 + } + fn item(&self, _list_model: &Self::Type, position: u32) -> Option { + self.list + .borrow() + .values() + .nth(position as usize) + .map(glib::object::Cast::upcast_ref::) + .cloned() + } + } +} + +glib::wrapper! { + pub struct RoomList(ObjectSubclass) + @implements gio::ListModel; +} + +impl Default for RoomList { + fn default() -> Self { + Self::new() + } +} + +impl RoomList { + pub fn new() -> Self { + glib::Object::new(&[]).expect("Failed to create RoomList") + } + + pub fn get(&self, room_id: &RoomId) -> Option { + let priv_ = imp::RoomList::from_instance(&self); + priv_.list.borrow().get(room_id).cloned() + } + + fn get_full(&self, room_id: &RoomId) -> Option<(usize, RoomId, Room)> { + let priv_ = imp::RoomList::from_instance(&self); + priv_ + .list + .borrow() + .get_full(room_id) + .map(|(pos, room_id, room)| (pos, room_id.clone(), room.clone())) + } + + pub fn contains_key(&self, room_id: &RoomId) -> bool { + let priv_ = imp::RoomList::from_instance(&self); + priv_.list.borrow().contains_key(room_id) + } + + pub fn insert(&self, rooms: Vec<(RoomId, Room)>) { + let priv_ = imp::RoomList::from_instance(&self); + + let rooms: Vec<(RoomId, Room)> = { + rooms + .into_iter() + .filter(|(room_id, _)| !priv_.list.borrow().contains_key(room_id)) + .collect() + }; + + let added = rooms.len(); + + if added > 0 { + let position = priv_.list.borrow().len(); + + { + let mut list = priv_.list.borrow_mut(); + for (room_id, room) in rooms { + room.connect_notify_local( + Some("category"), + clone!(@weak self as obj => move |r, _| { + if let Some((position, _, _)) = obj.get_full(r.matrix_room().room_id()) { + obj.items_changed(position as u32, 1, 1); + } + }), + ); + list.insert(room_id, room); + } + } + + self.items_changed(position as u32, 0, added as u32); + } + } + + pub fn remove(&self, room_id: &RoomId) { + let priv_ = imp::RoomList::from_instance(&self); + + let removed = { + let mut list = priv_.list.borrow_mut(); + + list.shift_remove_full(room_id) + }; + + if let Some((position, _, _)) = removed { + self.items_changed(position as u32, 1, 0); + } + } +} diff --git a/src/session/mod.rs b/src/session/mod.rs index d150dfd4..65aa6586 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -4,6 +4,7 @@ mod room; mod sidebar; mod user; +use self::categories::Categories; use self::content::Content; use self::room::Room; use self::sidebar::Sidebar; @@ -15,7 +16,6 @@ use crate::secret::StoredSession; use crate::utils::do_async; use crate::RUNTIME; -use crate::session::categories::Categories; use adw; use adw::subclass::prelude::BinImpl; use gtk::subclass::prelude::*; @@ -42,7 +42,6 @@ mod imp { use glib::subclass::{InitializingObject, Signal}; use once_cell::sync::{Lazy, OnceCell}; use std::cell::RefCell; - use std::collections::HashMap; #[derive(Debug, Default, CompositeTemplate)] #[template(resource = "/org/gnome/FractalNext/session.ui")] @@ -54,7 +53,6 @@ mod imp { /// Contains the error if something went wrong pub error: RefCell>, pub client: OnceCell, - pub rooms: RefCell>, pub categories: Categories, pub user: OnceCell, pub selected_room: RefCell>, @@ -358,16 +356,15 @@ impl Session { let rooms = priv_.client.get().unwrap().rooms(); let mut new_rooms = Vec::with_capacity(rooms.len()); - let mut rooms_map = priv_.rooms.borrow_mut(); for matrix_room in rooms { - let room_id = matrix_room.room_id().clone(); - let room = Room::new(matrix_room, self.user()); - rooms_map.insert(room_id, room.clone()); - new_rooms.push(room.clone()); + new_rooms.push(( + matrix_room.room_id().clone(), + Room::new(matrix_room, self.user()), + )); } - priv_.categories.append(new_rooms); + priv_.categories.room_list().insert(new_rooms); } /// Returns and consumes the `error` that was generated when the session failed to login, @@ -391,10 +388,9 @@ impl Session { fn handle_sync_response(&self, response: SyncResponse) { let priv_ = imp::Session::from_instance(self); + let rooms_map = priv_.categories.room_list(); let new_rooms_id: Vec = { - let rooms_map = priv_.rooms.borrow(); - let new_left_rooms = response.rooms.leave.iter().filter_map(|(room_id, _)| { if !rooms_map.contains_key(room_id) { Some(room_id) @@ -414,17 +410,14 @@ impl Session { }; let mut new_rooms = Vec::new(); - let mut rooms_map = priv_.rooms.borrow_mut(); for room_id in new_rooms_id { if let Some(matrix_room) = priv_.client.get().unwrap().get_room(&room_id) { - let room = Room::new(matrix_room, self.user()); - rooms_map.insert(room_id.clone(), room.clone()); - new_rooms.push(room.clone()); + new_rooms.push((room_id, Room::new(matrix_room, self.user()))); } } - priv_.categories.append(new_rooms); + rooms_map.insert(new_rooms); for (room_id, matrix_room) in response.rooms.leave { if matrix_room.timeline.events.is_empty() {