Browse Source

sidebar: Define subclass for item list

merge-requests/1327/merge
Kévin Commaille 4 years ago
parent
commit
35d3dd6d75
No known key found for this signature in database
GPG Key ID: DD507DAE96E8245C
  1. 10
      src/session/room/mod.rs
  2. 30
      src/session/sidebar/category.rs
  3. 14
      src/session/sidebar/entry.rs
  4. 120
      src/session/sidebar/item_list.rs
  5. 7
      src/session/sidebar/mod.rs
  6. 34
      src/session/sidebar/row.rs
  7. 163
      src/session/sidebar/sidebar_item.rs
  8. 12
      src/session/verification/identity_verification.rs

10
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 {

30
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;
}

14
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 {

120
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");

7
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,

34
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");
}

163
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)
}

12
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 {

Loading…
Cancel
Save