fractal/account_switcher/
session_item.rs

1use gtk::{glib, prelude::*, subclass::prelude::*, CompositeTemplate};
2
3use super::avatar_with_selection::AvatarWithSelection;
4use crate::{
5    components::AvatarData,
6    prelude::*,
7    session::model::Session,
8    session_list::{FailedSession, SessionInfo},
9};
10
11mod imp {
12    use std::{cell::RefCell, marker::PhantomData};
13
14    use glib::subclass::InitializingObject;
15
16    use super::*;
17
18    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
19    #[template(resource = "/org/gnome/Fractal/ui/account_switcher/session_item.ui")]
20    #[properties(wrapper_type = super::SessionItemRow)]
21    pub struct SessionItemRow {
22        #[template_child]
23        avatar: TemplateChild<AvatarWithSelection>,
24        #[template_child]
25        display_name: TemplateChild<gtk::Label>,
26        #[template_child]
27        user_id: TemplateChild<gtk::Label>,
28        #[template_child]
29        state_stack: TemplateChild<gtk::Stack>,
30        #[template_child]
31        error_image: TemplateChild<gtk::Image>,
32        /// The session this item represents.
33        #[property(get, set = Self::set_session, explicit_notify)]
34        session: glib::WeakRef<SessionInfo>,
35        user_bindings: RefCell<Vec<glib::Binding>>,
36        /// Whether this session is selected.
37        #[property(get = Self::is_selected, set = Self::set_selected, explicit_notify)]
38        selected: PhantomData<bool>,
39    }
40
41    #[glib::object_subclass]
42    impl ObjectSubclass for SessionItemRow {
43        const NAME: &'static str = "SessionItemRow";
44        type Type = super::SessionItemRow;
45        type ParentType = gtk::ListBoxRow;
46
47        fn class_init(klass: &mut Self::Class) {
48            Self::bind_template(klass);
49            Self::bind_template_callbacks(klass);
50        }
51
52        fn instance_init(obj: &InitializingObject<Self>) {
53            obj.init_template();
54        }
55    }
56
57    #[glib::derived_properties]
58    impl ObjectImpl for SessionItemRow {
59        fn dispose(&self) {
60            for binding in self.user_bindings.take() {
61                binding.unbind();
62            }
63        }
64    }
65
66    impl WidgetImpl for SessionItemRow {}
67    impl ListBoxRowImpl for SessionItemRow {}
68
69    #[gtk::template_callbacks]
70    impl SessionItemRow {
71        /// Whether this session is selected.
72        fn is_selected(&self) -> bool {
73            self.avatar.selected()
74        }
75
76        /// Set whether this session is selected.
77        fn set_selected(&self, selected: bool) {
78            if self.is_selected() == selected {
79                return;
80            }
81
82            self.avatar.set_selected(selected);
83
84            if selected {
85                self.display_name.add_css_class("bold");
86            } else {
87                self.display_name.remove_css_class("bold");
88            }
89
90            let obj = self.obj();
91            obj.update_state(&[gtk::accessible::State::Selected(Some(selected))]);
92
93            obj.notify_selected();
94        }
95
96        /// Set the session this item represents.
97        fn set_session(&self, session: Option<&SessionInfo>) {
98            if self.session.upgrade().as_ref() == session {
99                return;
100            }
101
102            for binding in self.user_bindings.take() {
103                binding.unbind();
104            }
105
106            if let Some(session) = session {
107                if let Some(session) = session.downcast_ref::<Session>() {
108                    let user = session.user();
109
110                    let avatar_data_handler = user
111                        .bind_property("avatar-data", &*self.avatar, "data")
112                        .sync_create()
113                        .build();
114                    let display_name_handler = user
115                        .bind_property("display-name", &*self.display_name, "label")
116                        .sync_create()
117                        .build();
118                    self.user_bindings
119                        .borrow_mut()
120                        .extend([avatar_data_handler, display_name_handler]);
121
122                    self.user_id.set_label(session.user_id().as_str());
123                    self.user_id.set_visible(true);
124
125                    self.state_stack.set_visible_child_name("settings");
126                } else {
127                    let user_id = session.user_id().to_string();
128
129                    let avatar_data = AvatarData::new();
130                    avatar_data.set_display_name(user_id.clone());
131                    self.avatar.set_data(Some(avatar_data));
132
133                    self.display_name.set_label(&user_id);
134                    self.user_id.set_visible(false);
135
136                    if let Some(failed) = session.downcast_ref::<FailedSession>() {
137                        self.error_image
138                            .set_tooltip_text(Some(&failed.error().to_user_facing()));
139                        self.state_stack.set_visible_child_name("error");
140                    } else {
141                        self.state_stack.set_visible_child_name("loading");
142                    }
143                }
144            }
145
146            self.session.set(session);
147            self.obj().notify_session();
148        }
149
150        /// Show the account settings for the session of this row.
151        #[template_callback]
152        fn show_account_settings(&self) {
153            let Some(session) = self.session.upgrade() else {
154                return;
155            };
156
157            let obj = self.obj();
158            obj.activate_action("account-switcher.close", None)
159                .expect("`account-switcher.close` action should exist");
160            obj.activate_action(
161                "win.open-account-settings",
162                Some(&session.session_id().to_variant()),
163            )
164            .expect("`win.open-account-settings` action should exist");
165        }
166    }
167}
168
169glib::wrapper! {
170    /// A `GtkListBoxRow` representing a logged-in session.
171    pub struct SessionItemRow(ObjectSubclass<imp::SessionItemRow>)
172        @extends gtk::Widget, gtk::ListBoxRow, @implements gtk::Accessible;
173}
174
175impl SessionItemRow {
176    pub fn new(session: &SessionInfo) -> Self {
177        glib::Object::builder().property("session", session).build()
178    }
179}