[fractal] sidebar: Define subclass for item list
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal] sidebar: Define subclass for item list
- Date: Mon, 11 Apr 2022 16:46:37 +0000 (UTC)
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]