diff --git a/src/session/view/create_dm_dialog/dm_user.rs b/src/session/view/create_dm_dialog/dm_user.rs index f6e32b8a..83dba1d6 100644 --- a/src/session/view/create_dm_dialog/dm_user.rs +++ b/src/session/view/create_dm_dialog/dm_user.rs @@ -8,12 +8,13 @@ use crate::{ }; mod imp { - use once_cell::sync::Lazy; - use super::*; - #[derive(Debug, Default)] + #[derive(Debug, Default, glib::Properties)] + #[properties(wrapper_type = super::DmUser)] pub struct DmUser { + /// The direct chat with this user, if any. + #[property(get, set = Self::set_direct_chat, explicit_notify, nullable)] pub direct_chat: glib::WeakRef, } @@ -24,36 +25,30 @@ mod imp { type ParentType = User; } + #[glib::derived_properties] impl ObjectImpl for DmUser { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![glib::ParamSpecObject::builder::("direct-chat") - .read_only() - .build()] - }); - - PROPERTIES.as_ref() - } - - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - let obj = self.obj(); - - match pspec.name() { - "direct-chat" => obj.direct_chat().to_value(), - _ => unimplemented!(), - } - } - fn constructed(&self) { self.parent_constructed(); let obj = self.obj(); spawn!(clone!(@weak obj => async move { let direct_chat = obj.upcast_ref::().direct_chat().await; - obj.set_direct_chat(direct_chat.as_ref()); + obj.set_direct_chat(direct_chat); })); } } + + impl DmUser { + /// Set the direct chat with this user. + fn set_direct_chat(&self, direct_chat: Option) { + if self.direct_chat.upgrade() == direct_chat { + return; + } + + self.direct_chat.set(direct_chat.as_ref()); + self.obj().notify_direct_chat(); + } + } } glib::wrapper! { @@ -77,19 +72,4 @@ impl DmUser { obj.set_avatar_url(avatar_url.map(std::borrow::ToOwned::to_owned)); obj } - - /// Get the direct chat with this user, if any. - pub fn direct_chat(&self) -> Option { - self.imp().direct_chat.upgrade() - } - - /// Set the direct chat with this user. - fn set_direct_chat(&self, direct_chat: Option<&Room>) { - if self.direct_chat().as_ref() == direct_chat { - return; - } - - self.imp().direct_chat.set(direct_chat); - self.notify("direct-chat"); - } } diff --git a/src/session/view/create_dm_dialog/dm_user_list.rs b/src/session/view/create_dm_dialog/dm_user_list.rs index e5c6f07e..092ceb5a 100644 --- a/src/session/view/create_dm_dialog/dm_user_list.rs +++ b/src/session/view/create_dm_dialog/dm_user_list.rs @@ -21,15 +21,21 @@ mod imp { use std::cell::{Cell, RefCell}; use futures_util::future::AbortHandle; - use once_cell::sync::Lazy; use super::*; - #[derive(Debug, Default)] + #[derive(Debug, Default, glib::Properties)] + #[properties(wrapper_type = super::DmUserList)] pub struct DmUserList { pub list: RefCell>, + /// The current session. + #[property(get, construct_only)] pub session: glib::WeakRef, + /// The state of the list. + #[property(get, builder(DmUserListState::default()))] pub state: Cell, + /// The search term. + #[property(get, set = Self::set_search_term, explicit_notify, nullable)] pub search_term: RefCell>, pub abort_handle: RefCell>, } @@ -41,52 +47,18 @@ mod imp { type Interfaces = (gio::ListModel,); } - impl ObjectImpl for DmUserList { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![ - glib::ParamSpecObject::builder::("session") - .construct_only() - .build(), - glib::ParamSpecString::builder("search-term") - .explicit_notify() - .build(), - glib::ParamSpecEnum::builder::("state") - .read_only() - .build(), - ] - }); - - PROPERTIES.as_ref() - } - - fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { - match pspec.name() { - "session" => self.session.set(value.get().unwrap()), - "search-term" => self.obj().set_search_term(value.get().unwrap()), - _ => unimplemented!(), - } - } - - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - let obj = self.obj(); - - match pspec.name() { - "session" => obj.session().to_value(), - "search-term" => obj.search_term().to_value(), - "state" => obj.state().to_value(), - _ => unimplemented!(), - } - } - } + #[glib::derived_properties] + impl ObjectImpl for DmUserList {} impl ListModelImpl for DmUserList { fn item_type(&self) -> glib::Type { DmUser::static_type() } + fn n_items(&self) -> u32 { self.list.borrow().len() as u32 } + fn item(&self, position: u32) -> Option { self.list .borrow() @@ -95,6 +67,26 @@ mod imp { .and_upcast() } } + + impl DmUserList { + /// Set the search term. + fn set_search_term(&self, search_term: Option) { + let search_term = search_term.filter(|s| !s.is_empty()); + + if search_term.as_ref() == self.search_term.borrow().as_ref() { + return; + } + let obj = self.obj(); + + self.search_term.replace(search_term); + + spawn!(clone!(@weak obj => async move { + obj.search_users().await; + })); + + obj.notify_search_term(); + } + } } glib::wrapper! { @@ -108,34 +100,6 @@ impl DmUserList { glib::Object::builder().property("session", session).build() } - /// The session this list refers to. - pub fn session(&self) -> Session { - self.imp().session.upgrade().unwrap() - } - - /// Set the search term. - pub fn set_search_term(&self, search_term: Option) { - let imp = self.imp(); - let search_term = search_term.filter(|s| !s.is_empty()); - - if search_term.as_ref() == imp.search_term.borrow().as_ref() { - return; - } - - imp.search_term.replace(search_term); - - spawn!(clone!(@weak self as obj => async move { - obj.search_users().await; - })); - - self.notify("search_term"); - } - - /// The search term. - fn search_term(&self) -> Option { - self.imp().search_term.borrow().clone() - } - /// Set the state of the list. fn set_state(&self, state: DmUserListState) { let imp = self.imp(); @@ -148,11 +112,6 @@ impl DmUserList { self.notify("state"); } - /// The state of the list. - pub fn state(&self) -> DmUserListState { - self.imp().state.get() - } - fn set_list(&self, users: Vec) { let added = users.len(); @@ -166,7 +125,9 @@ impl DmUserList { } async fn search_users(&self) { - let session = self.session(); + let Some(session) = self.session() else { + return; + }; let client = session.client(); let Some(search_term) = self.search_term() else { self.set_state(DmUserListState::Initial); diff --git a/src/session/view/create_dm_dialog/dm_user_row.rs b/src/session/view/create_dm_dialog/dm_user_row.rs index e6f0b816..1fa3ce50 100644 --- a/src/session/view/create_dm_dialog/dm_user_row.rs +++ b/src/session/view/create_dm_dialog/dm_user_row.rs @@ -6,13 +6,15 @@ mod imp { use std::cell::RefCell; use glib::subclass::InitializingObject; - use once_cell::sync::Lazy; use super::*; - #[derive(Debug, Default, CompositeTemplate)] + #[derive(Debug, Default, CompositeTemplate, glib::Properties)] #[template(resource = "/org/gnome/Fractal/ui/session/view/create_dm_dialog/dm_user_row.ui")] + #[properties(wrapper_type = super::DmUserRow)] pub struct DmUserRow { + /// The user displayed by this row. + #[property(get, set = Self::set_user, explicit_notify)] pub user: RefCell>, } @@ -31,36 +33,27 @@ mod imp { } } - impl ObjectImpl for DmUserRow { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![glib::ParamSpecObject::builder::("user") - .explicit_notify() - .build()] - }); + #[glib::derived_properties] + impl ObjectImpl for DmUserRow {} - PROPERTIES.as_ref() - } + impl WidgetImpl for DmUserRow {} + impl ListBoxRowImpl for DmUserRow {} - fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { - match pspec.name() { - "user" => self.obj().set_user(value.get().unwrap()), - _ => unimplemented!(), + impl DmUserRow { + /// Set the user displayed by this row. + fn set_user(&self, user: Option) { + if self.user.borrow().clone() == user { + return; } - } - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - match pspec.name() { - "user" => self.obj().user().to_value(), - _ => unimplemented!(), - } + self.user.replace(user); + self.obj().notify_user(); } } - impl WidgetImpl for DmUserRow {} - impl ListBoxRowImpl for DmUserRow {} } glib::wrapper! { + /// A row of the DM user list. pub struct DmUserRow(ObjectSubclass) @extends gtk::Widget, gtk::ListBoxRow, @implements gtk::Accessible; } @@ -69,22 +62,4 @@ impl DmUserRow { pub fn new(user: &DmUser) -> Self { glib::Object::builder().property("user", user).build() } - - /// The user displayed by this row. - pub fn user(&self) -> Option { - self.imp().user.borrow().clone() - } - - /// Set the user displayed by this row. - pub fn set_user(&self, user: Option) { - let imp = self.imp(); - let prev_user = self.user(); - - if prev_user == user { - return; - } - - imp.user.replace(user); - self.notify("user"); - } } diff --git a/src/session/view/create_dm_dialog/mod.rs b/src/session/view/create_dm_dialog/mod.rs index 80c4fc87..fd898a18 100644 --- a/src/session/view/create_dm_dialog/mod.rs +++ b/src/session/view/create_dm_dialog/mod.rs @@ -17,14 +17,17 @@ use crate::{ }; mod imp { - use glib::{object::WeakRef, subclass::InitializingObject}; + use glib::subclass::InitializingObject; use super::*; - #[derive(Debug, Default, CompositeTemplate)] + #[derive(Debug, Default, CompositeTemplate, glib::Properties)] #[template(resource = "/org/gnome/Fractal/ui/session/view/create_dm_dialog/mod.ui")] + #[properties(wrapper_type = super::CreateDmDialog)] pub struct CreateDmDialog { - pub session: WeakRef, + /// The current session. + #[property(get, set = Self::set_session, explicit_notify)] + pub session: glib::WeakRef, #[template_child] pub list_box: TemplateChild, #[template_child] @@ -59,40 +62,56 @@ mod imp { } } - impl ObjectImpl for CreateDmDialog { - fn properties() -> &'static [glib::ParamSpec] { - use once_cell::sync::Lazy; - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![glib::ParamSpecObject::builder::("session") - .explicit_notify() - .build()] - }); + #[glib::derived_properties] + impl ObjectImpl for CreateDmDialog {} - PROPERTIES.as_ref() - } + impl WidgetImpl for CreateDmDialog {} + impl WindowImpl for CreateDmDialog {} + impl AdwWindowImpl for CreateDmDialog {} - fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { - match pspec.name() { - "session" => self.obj().set_session(value.get().unwrap()), - _ => unimplemented!(), + impl CreateDmDialog { + /// Set the current session. + pub fn set_session(&self, session: Option) { + if self.session.upgrade() == session { + return; } - } + let obj = self.obj(); + + if let Some(session) = &session { + let user_list = DmUserList::new(session); - fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - match pspec.name() { - "session" => self.obj().session().to_value(), - _ => unimplemented!(), + // We don't need to disconnect this signal since the `DmUserList` will be + // disposed once unbound from the `gtk::ListBox` + user_list.connect_state_notify(clone!(@weak obj => move |model| { + obj.update_view(model); + })); + + self.search_entry + .bind_property("text", &user_list, "search-term") + .sync_create() + .build(); + + self.list_box.bind_model(Some(&user_list), |user| { + DmUserRow::new( + user.downcast_ref::() + .expect("DmUserList must contain only `DmUser`"), + ) + .upcast() + }); + + obj.update_view(&user_list); + } else { + self.list_box.unbind_model(); } + + self.session.set(session.as_ref()); + obj.notify_session(); } } - - impl WidgetImpl for CreateDmDialog {} - impl WindowImpl for CreateDmDialog {} - impl AdwWindowImpl for CreateDmDialog {} } glib::wrapper! { - /// Preference Window to display and update room details. + /// Dialog to create a new direct chat. pub struct CreateDmDialog(ObjectSubclass) @extends gtk::Widget, gtk::Window, adw::Window, adw::Bin, @implements gtk::Accessible; } @@ -106,53 +125,6 @@ impl CreateDmDialog { .build() } - /// The current session. - pub fn session(&self) -> Option { - self.imp().session.upgrade() - } - - /// Set the current session. - pub fn set_session(&self, session: Option) { - let imp = self.imp(); - - if self.session() == session { - return; - } - - if let Some(ref session) = session { - let user_list = DmUserList::new(session); - - // We don't need to disconnect this signal since the `DmUserList` will be - // disposed once unbound from the `gtk::ListBox` - user_list.connect_notify_local( - Some("state"), - clone!(@weak self as obj => move |model, _| { - obj.update_view(model); - }), - ); - - imp.search_entry - .bind_property("text", &user_list, "search-term") - .sync_create() - .build(); - - imp.list_box.bind_model(Some(&user_list), |user| { - DmUserRow::new( - user.downcast_ref::() - .expect("DmUserList must contain only `DmUser`"), - ) - .upcast() - }); - - self.update_view(&user_list); - } else { - imp.list_box.unbind_model(); - } - - imp.session.set(session.as_ref()); - self.notify("session"); - } - fn update_view(&self, model: &DmUserList) { let visible_child_name = match model.state() { DmUserListState::Initial => "no-search-page",