fractal/account_switcher/
session_item.rs1use 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 #[property(get, set = Self::set_session, explicit_notify)]
34 session: glib::WeakRef<SessionInfo>,
35 user_bindings: RefCell<Vec<glib::Binding>>,
36 #[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 fn is_selected(&self) -> bool {
73 self.avatar.selected()
74 }
75
76 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 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 #[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 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}