[fractal/fractal-next] session: Use a single room list with GtkFilterListModels
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] session: Use a single room list with GtkFilterListModels
- Date: Wed, 19 May 2021 06:24:34 +0000 (UTC)
commit f0f0c4cbb9c43a26b0f3b32810a56f885dc6e46f
Author: Kévin Commaille <46488-zecakeh1 users noreply gitlab gnome org>
Date: Wed May 19 06:24:29 2021 +0000
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(-)
---
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<HashMap<Room, CategoryType>>,
+ 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<Room>) {
+ pub fn room_list(&self) -> &RoomList {
let priv_ = imp::Categories::from_instance(&self);
-
- let rooms: Vec<Room> = {
- 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<Vec<Room>>,
+ pub model: OnceCell<gtk::FilterListModel>,
pub type_: Cell<CategoryType>,
}
@@ -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<glib::Object> {
- self.list
- .borrow()
- .get(position as usize)
- .map(|o| o.clone().upcast::<glib::Object>())
+ 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<Room>) {
- 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::<Room>()
+ .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<IndexMap<RoomId, Room>>,
+ }
+
+ #[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<glib::Object> {
+ self.list
+ .borrow()
+ .values()
+ .nth(position as usize)
+ .map(glib::object::Cast::upcast_ref::<glib::Object>)
+ .cloned()
+ }
+ }
+}
+
+glib::wrapper! {
+ pub struct RoomList(ObjectSubclass<imp::RoomList>)
+ @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<Room> {
+ 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<Option<matrix_sdk::Error>>,
pub client: OnceCell<Client>,
- pub rooms: RefCell<HashMap<RoomId, Room>>,
pub categories: Categories,
pub user: OnceCell<User>,
pub selected_room: RefCell<Option<Room>>,
@@ -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<RoomId> = {
- 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() {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]