Browse Source

session: Use a single room list with GtkFilterListModels

merge-requests/1327/merge
Kévin Commaille 5 years ago committed by Julian Sparber
parent
commit
f0f0c4cbb9
  1. 1
      Cargo.lock
  2. 1
      Cargo.toml
  3. 74
      src/session/categories/categories.rs
  4. 96
      src/session/categories/category.rs
  5. 2
      src/session/categories/mod.rs
  6. 132
      src/session/categories/room_list.rs
  7. 25
      src/session/mod.rs

1
Cargo.lock generated

@ -678,6 +678,7 @@ dependencies = [
"gtk-macros",
"gtk4",
"html2pango",
"indexmap",
"libadwaita",
"log",
"matrix-sdk",

1
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"

74
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
}
}

96
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);
}
}

2
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;

132
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);
}
}
}

25
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() {

Loading…
Cancel
Save