Merge branch 'session-view-properties' into 'main'

session-view: Port more modules to glib::Properties macro

See merge request GNOME/fractal!1499
This commit is contained in:
Kévin Commaille 2023-12-17 22:07:52 +00:00
commit 51d9978dc5
11 changed files with 275 additions and 573 deletions

View File

@ -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<Room>,
}
@ -24,36 +25,30 @@ mod imp {
type ParentType = User;
}
#[glib::derived_properties]
impl ObjectImpl for DmUser {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<Room>("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::<User>().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<Room>) {
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<Room> {
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");
}
}

View File

@ -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<Vec<DmUser>>,
/// The current session.
#[property(get, construct_only)]
pub session: glib::WeakRef<Session>,
/// The state of the list.
#[property(get, builder(DmUserListState::default()))]
pub state: Cell<DmUserListState>,
/// The search term.
#[property(get, set = Self::set_search_term, explicit_notify, nullable)]
pub search_term: RefCell<Option<String>>,
pub abort_handle: RefCell<Option<AbortHandle>>,
}
@ -41,52 +47,18 @@ mod imp {
type Interfaces = (gio::ListModel,);
}
impl ObjectImpl for DmUserList {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecObject::builder::<Session>("session")
.construct_only()
.build(),
glib::ParamSpecString::builder("search-term")
.explicit_notify()
.build(),
glib::ParamSpecEnum::builder::<DmUserListState>("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<glib::Object> {
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<String>) {
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<String>) {
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<String> {
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<DmUser>) {
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);

View File

@ -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<Option<DmUser>>,
}
@ -31,36 +33,27 @@ mod imp {
}
}
impl ObjectImpl for DmUserRow {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<DmUser>("user")
.explicit_notify()
.build()]
});
#[glib::derived_properties]
impl ObjectImpl for DmUserRow {}
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"user" => self.obj().set_user(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"user" => self.obj().user().to_value(),
_ => unimplemented!(),
}
}
}
impl WidgetImpl for DmUserRow {}
impl ListBoxRowImpl for DmUserRow {}
impl DmUserRow {
/// Set the user displayed by this row.
fn set_user(&self, user: Option<DmUser>) {
if self.user.borrow().clone() == user {
return;
}
self.user.replace(user);
self.obj().notify_user();
}
}
}
glib::wrapper! {
/// A row of the DM user list.
pub struct DmUserRow(ObjectSubclass<imp::DmUserRow>)
@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<DmUser> {
self.imp().user.borrow().clone()
}
/// Set the user displayed by this row.
pub fn set_user(&self, user: Option<DmUser>) {
let imp = self.imp();
let prev_user = self.user();
if prev_user == user {
return;
}
imp.user.replace(user);
self.notify("user");
}
}

View File

@ -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<Session>,
/// The current session.
#[property(get, set = Self::set_session, explicit_notify)]
pub session: glib::WeakRef<Session>,
#[template_child]
pub list_box: TemplateChild<gtk::ListBox>,
#[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<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<Session>("session")
.explicit_notify()
.build()]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"session" => self.obj().set_session(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"session" => self.obj().session().to_value(),
_ => unimplemented!(),
}
}
}
#[glib::derived_properties]
impl ObjectImpl for CreateDmDialog {}
impl WidgetImpl for CreateDmDialog {}
impl WindowImpl for CreateDmDialog {}
impl AdwWindowImpl for CreateDmDialog {}
impl CreateDmDialog {
/// Set the current session.
pub fn set_session(&self, session: Option<Session>) {
if self.session.upgrade() == session {
return;
}
let obj = self.obj();
if let Some(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_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::<DmUser>()
.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();
}
}
}
glib::wrapper! {
/// Preference Window to display and update room details.
/// Dialog to create a new direct chat.
pub struct CreateDmDialog(ObjectSubclass<imp::CreateDmDialog>)
@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<Session> {
self.imp().session.upgrade()
}
/// Set the current session.
pub fn set_session(&self, session: Option<Session>) {
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::<DmUser>()
.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",

View File

@ -5,15 +5,19 @@ use sourceview::prelude::*;
use crate::session::model::Event;
mod imp {
use std::cell::RefCell;
use glib::subclass::InitializingObject;
use once_cell::unsync::OnceCell;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/session/view/event_source_dialog.ui")]
#[properties(wrapper_type = super::EventSourceDialog)]
pub struct EventSourceDialog {
pub event: OnceCell<Event>,
/// The event that is displayed in the dialog.
#[property(get, construct_only)]
pub event: RefCell<Option<Event>>,
#[template_child]
pub source_view: TemplateChild<sourceview::View>,
}
@ -43,34 +47,8 @@ mod imp {
}
}
#[glib::derived_properties]
impl ObjectImpl for EventSourceDialog {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<Event>("event")
.construct_only()
.build()]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"event" => {
let _ = self.event.set(value.get().unwrap());
}
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"event" => self.obj().event().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self) {
let buffer = self
.source_view
@ -104,11 +82,6 @@ impl EventSourceDialog {
.build()
}
/// The event that is displayed in the dialog.
pub fn event(&self) -> Option<&Event> {
self.imp().event.get()
}
pub fn copy_to_clipboard(&self) {
let clipboard = self.clipboard();
let buffer = self.imp().source_view.buffer();

View File

@ -9,14 +9,17 @@ use ruma::{
use crate::{session::model::Session, spawn, toast, Window};
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/join_room_dialog.ui")]
#[properties(wrapper_type = super::JoinRoomDialog)]
pub struct JoinRoomDialog {
pub session: WeakRef<Session>,
/// The current session.
#[property(get, set = Self::set_session, explicit_notify)]
pub session: glib::WeakRef<Session>,
#[template_child]
pub entry: TemplateChild<gtk::Entry>,
}
@ -44,32 +47,8 @@ mod imp {
}
}
impl ObjectImpl for JoinRoomDialog {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<Session>("session")
.explicit_notify()
.build()]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"session" => self.obj().set_session(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"session" => self.obj().session().to_value(),
_ => unimplemented!(),
}
}
}
#[glib::derived_properties]
impl ObjectImpl for JoinRoomDialog {}
impl WidgetImpl for JoinRoomDialog {}
impl WindowImpl for JoinRoomDialog {}
@ -83,6 +62,18 @@ mod imp {
self.parent_response(response)
}
}
impl JoinRoomDialog {
/// Set the current session.
fn set_session(&self, session: Option<Session>) {
if self.session.upgrade() == session {
return;
}
self.session.set(session.as_ref());
self.obj().notify_session();
}
}
}
glib::wrapper! {
@ -100,23 +91,6 @@ impl JoinRoomDialog {
.build()
}
/// The current session.
pub fn session(&self) -> Option<Session> {
self.imp().session.upgrade()
}
/// Set the current session.
pub fn set_session(&self, session: Option<&Session>) {
let imp = self.imp();
if self.session().as_ref() == session {
return;
}
imp.session.set(session);
self.notify("session");
}
/// Handle when the entry text changed.
#[template_callback]
fn entry_changed(&self, entry: &gtk::Entry) {

View File

@ -19,25 +19,31 @@ const CANCEL_SWIPE_ANIMATION_DURATION: u32 = 400;
mod imp {
use std::{
cell::{Cell, RefCell},
cell::{Cell, OnceCell, RefCell},
collections::HashMap,
};
use glib::{object::WeakRef, subclass::InitializingObject};
use once_cell::{sync::Lazy, unsync::OnceCell};
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/session/view/media_viewer.ui")]
#[properties(wrapper_type = super::MediaViewer)]
pub struct MediaViewer {
/// Whether the viewer is fullscreened.
#[property(get, set = Self::set_fullscreened, explicit_notify)]
pub fullscreened: Cell<bool>,
/// The room containing the media message.
pub room: WeakRef<Room>,
#[property(get)]
pub room: glib::WeakRef<Room>,
/// The ID of the event containing the media message.
#[property(get = Self::event_id, type = Option<String>)]
pub event_id: RefCell<Option<OwnedEventId>>,
/// The media message to display.
pub message: RefCell<Option<MessageType>>,
/// The body of the media event.
#[property(get)]
pub body: RefCell<Option<String>>,
pub animation: OnceCell<adw::TimedAnimation>,
pub swipe_tracker: OnceCell<adw::SwipeTracker>,
@ -113,47 +119,8 @@ mod imp {
}
}
#[glib::derived_properties]
impl ObjectImpl for MediaViewer {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecBoolean::builder("fullscreened")
.explicit_notify()
.build(),
glib::ParamSpecObject::builder::<Room>("room")
.read_only()
.build(),
glib::ParamSpecString::builder("event-id")
.read_only()
.build(),
glib::ParamSpecString::builder("body").read_only().build(),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
let obj = self.obj();
match pspec.name() {
"fullscreened" => obj.set_fullscreened(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
let obj = self.obj();
match pspec.name() {
"fullscreened" => obj.fullscreened().to_value(),
"room" => obj.room().to_value(),
"event-id" => obj.event_id().as_ref().map(|e| e.as_str()).to_value(),
"body" => obj.body().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self) {
self.parent_constructed();
@ -277,9 +244,38 @@ mod imp {
gdk::Rectangle::new(0, 0, self.obj().width(), self.obj().height())
}
}
impl MediaViewer {
/// Set whether the viewer is fullscreened.
fn set_fullscreened(&self, fullscreened: bool) {
if fullscreened == self.fullscreened.get() {
return;
}
self.fullscreened.set(fullscreened);
if fullscreened {
// Upscale the media on fullscreen
self.media.set_halign(gtk::Align::Fill);
self.toolbar_view
.set_top_bar_style(adw::ToolbarStyle::Raised);
} else {
self.media.set_halign(gtk::Align::Center);
self.toolbar_view.set_top_bar_style(adw::ToolbarStyle::Flat);
}
self.obj().notify_fullscreened();
}
/// The ID of the event containing the media message.
fn event_id(&self) -> Option<String> {
self.event_id.borrow().as_ref().map(ToString::to_string)
}
}
}
glib::wrapper! {
/// A widget allowing to view a media file.
pub struct MediaViewer(ObjectSubclass<imp::MediaViewer>)
@extends gtk::Widget, @implements gtk::Accessible, adw::Swipeable;
}
@ -308,16 +304,6 @@ impl MediaViewer {
animation.play();
}
/// The room containing the media message.
pub fn room(&self) -> Option<Room> {
self.imp().room.upgrade()
}
/// The ID of the event containing the media message.
pub fn event_id(&self) -> Option<OwnedEventId> {
self.imp().event_id.borrow().clone()
}
/// The media message to display.
pub fn message(&self) -> Option<MessageType> {
self.imp().message.borrow().clone()
@ -333,13 +319,8 @@ impl MediaViewer {
self.update_menu_actions();
self.build();
self.notify("room");
self.notify("event-id");
}
/// The body of the media event.
pub fn body(&self) -> Option<String> {
self.imp().body.borrow().clone()
self.notify_room();
self.notify_event_id();
}
/// Set the body of the media event.
@ -349,35 +330,7 @@ impl MediaViewer {
}
self.imp().body.replace(body);
self.notify("body");
}
/// Whether the viewer is fullscreened.
pub fn fullscreened(&self) -> bool {
self.imp().fullscreened.get()
}
/// Set whether the viewer is fullscreened.
pub fn set_fullscreened(&self, fullscreened: bool) {
let imp = self.imp();
if fullscreened == self.fullscreened() {
return;
}
imp.fullscreened.set(fullscreened);
if fullscreened {
// Upscale the media on fullscreen
imp.media.set_halign(gtk::Align::Fill);
imp.toolbar_view
.set_top_bar_style(adw::ToolbarStyle::Raised);
} else {
imp.media.set_halign(gtk::Align::Center);
imp.toolbar_view.set_top_bar_style(adw::ToolbarStyle::Flat);
}
self.notify("fullscreened");
self.notify_body();
}
/// Update the actions of the menu according to the current message.
@ -566,7 +519,7 @@ impl MediaViewer {
let Some(room) = self.room() else {
return;
};
let Some(event_id) = self.event_id() else {
let Some(event_id) = self.imp().event_id.borrow().clone() else {
return;
};
let matrix_room = room.matrix_room();

View File

@ -25,14 +25,17 @@ use crate::{
const MAX_BYTES: usize = 255;
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/room_creation.ui")]
#[properties(wrapper_type = super::RoomCreation)]
pub struct RoomCreation {
pub session: WeakRef<Session>,
/// The current session.
#[property(get, set = Self::set_session, explicit_notify, nullable)]
pub session: glib::WeakRef<Session>,
#[template_child]
pub create_button: TemplateChild<SpinnerButton>,
#[template_child]
@ -78,41 +81,34 @@ mod imp {
}
}
impl ObjectImpl for RoomCreation {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<Session>("session")
.explicit_notify()
.build()]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"session" => self.obj().set_session(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"session" => self.obj().session().to_value(),
_ => unimplemented!(),
}
}
}
#[glib::derived_properties]
impl ObjectImpl for RoomCreation {}
impl WidgetImpl for RoomCreation {}
impl WindowImpl for RoomCreation {}
impl AdwWindowImpl for RoomCreation {}
impl ToastableWindowImpl for RoomCreation {}
impl RoomCreation {
/// Set the current session.
fn set_session(&self, session: Option<Session>) {
if self.session.upgrade() == session {
return;
}
if let Some(session) = &session {
self.server_name
.set_label(&format!(":{}", session.user_id().server_name()));
}
self.session.set(session.as_ref());
self.obj().notify_session();
}
}
}
glib::wrapper! {
/// Preference Window to display and update room details.
/// Dialog to create a new room.
pub struct RoomCreation(ObjectSubclass<imp::RoomCreation>)
@extends gtk::Widget, gtk::Window, adw::Window, ToastableWindow, @implements gtk::Accessible;
}
@ -126,28 +122,6 @@ impl RoomCreation {
.build()
}
/// The current session.
pub fn session(&self) -> Option<Session> {
self.imp().session.upgrade()
}
/// Set the current session.
pub fn set_session(&self, session: Option<&Session>) {
let imp = self.imp();
if self.session().as_ref() == session {
return;
}
if let Some(session) = session {
imp.server_name
.set_label(&format!(":{}", session.user_id().server_name()));
}
imp.session.set(session);
self.notify("session");
}
/// Create the room, if it is allowed.
#[template_callback]
fn create_room(&self) {

View File

@ -17,12 +17,12 @@ 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/session_view.ui")]
#[properties(wrapper_type = super::SessionView)]
pub struct SessionView {
#[template_child]
pub stack: TemplateChild<gtk::Stack>,
@ -36,6 +36,8 @@ mod imp {
pub content: TemplateChild<Content>,
#[template_child]
pub media_viewer: TemplateChild<MediaViewer>,
/// The current session.
#[property(get, set = Self::set_session, explicit_notify, nullable)]
pub session: glib::WeakRef<Session>,
pub window_active_handler_id: RefCell<Option<SignalHandlerId>>,
}
@ -115,35 +117,8 @@ mod imp {
}
}
#[glib::derived_properties]
impl ObjectImpl for SessionView {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<Session>("session")
.explicit_notify()
.build()]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
let obj = self.obj();
match pspec.name() {
"session" => obj.set_session(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(),
_ => unimplemented!(),
}
}
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
@ -206,6 +181,18 @@ mod imp {
impl WidgetImpl for SessionView {}
impl BinImpl for SessionView {}
impl SessionView {
/// Set the current session.
fn set_session(&self, session: Option<Session>) {
if self.session.upgrade() == session {
return;
}
self.session.set(session.as_ref());
self.obj().notify_session();
}
}
}
glib::wrapper! {
@ -215,26 +202,11 @@ glib::wrapper! {
}
impl SessionView {
/// Create a new session.
/// Create a new session view.
pub async fn new() -> Self {
glib::Object::new()
}
/// The Matrix user session.
pub fn session(&self) -> Option<Session> {
self.imp().session.upgrade()
}
/// Set the Matrix user session.
pub fn set_session(&self, session: Option<&Session>) {
if self.session().as_ref() == session {
return;
}
self.imp().session.set(session);
self.notify("session");
}
/// The currently selected room, if any.
pub fn selected_room(&self) -> Option<Room> {
self.imp().content.item().and_downcast()

View File

@ -18,8 +18,9 @@ mod imp {
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/session/view/user_page.ui")]
#[properties(wrapper_type = super::UserPage)]
pub struct UserPage {
#[template_child]
pub avatar: TemplateChild<Avatar>,
@ -32,6 +33,7 @@ mod imp {
#[template_child]
pub verify_button: TemplateChild<SpinnerButton>,
/// The current user.
#[property(get, set = Self::set_user, construct_only)]
pub user: BoundObject<User>,
pub bindings: RefCell<Vec<glib::Binding>>,
}
@ -63,32 +65,8 @@ mod imp {
}
}
#[glib::derived_properties]
impl ObjectImpl for UserPage {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::<User>("user")
.construct_only()
.build()]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"user" => self.obj().set_user(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"user" => self.obj().user().to_value(),
_ => unimplemented!(),
}
}
fn dispose(&self) {
for binding in self.bindings.take() {
binding.unbind();
@ -98,6 +76,38 @@ mod imp {
impl WidgetImpl for UserPage {}
impl NavigationPageImpl for UserPage {}
impl UserPage {
/// Set the current user.
fn set_user(&self, user: User) {
let obj = self.obj();
let title_binding = user
.bind_property("display-name", &*obj, "title")
.sync_create()
.build();
let avatar_binding = user
.bind_property("avatar-data", &*self.avatar, "data")
.sync_create()
.build();
self.bindings.replace(vec![title_binding, avatar_binding]);
let is_verified_handler = user.connect_verified_notify(clone!(@weak obj => move |_| {
obj.update_verified();
}));
// We don't need to listen to changes of the property, it never changes after
// construction.
self.direct_chat_button.set_visible(!user.is_own_user());
self.user.set(user, vec![is_verified_handler]);
spawn!(clone!(@weak obj => async move {
obj.load_direct_chat().await;
}));
obj.update_verified();
}
}
}
glib::wrapper! {
@ -112,48 +122,6 @@ impl UserPage {
glib::Object::builder().property("user", user).build()
}
/// The current user.
pub fn user(&self) -> Option<User> {
self.imp().user.obj()
}
/// Set the current user.
fn set_user(&self, user: Option<User>) {
let Some(user) = user else {
// Ignore missing user.
return;
};
let imp = self.imp();
let title_binding = user
.bind_property("display-name", self, "title")
.sync_create()
.build();
let avatar_binding = user
.bind_property("avatar-data", &*imp.avatar, "data")
.sync_create()
.build();
imp.bindings.replace(vec![title_binding, avatar_binding]);
let is_verified_handler = user.connect_notify_local(
Some("is-verified"),
clone!(@weak self as obj => move |_, _| {
obj.update_verified();
}),
);
// We don't need to listen to changes of the property, it never changes after
// construction.
imp.direct_chat_button.set_visible(!user.is_own_user());
imp.user.set(user, vec![is_verified_handler]);
spawn!(clone!(@weak self as obj => async move {
obj.load_direct_chat().await;
}));
self.update_verified();
}
/// Load whether the current user has a direct chat or not.
async fn load_direct_chat(&self) {
self.set_direct_chat_loading(true);

View File

@ -343,7 +343,7 @@ impl Window {
self.switch_to_loading_page();
}
imp.session.set_session(None);
imp.session.set_session(None::<Session>);
}
pub fn save_window_size(&self) -> Result<(), glib::BoolError> {