fractal/session_view/sidebar/
section_row.rs1use adw::{prelude::*, subclass::prelude::*};
2use gettextrs::gettext;
3use gtk::{glib, glib::clone};
4
5use crate::{
6 session::{HighlightFlags, RoomCategory, SidebarSection, SidebarSectionName},
7 utils::{BoundObject, TemplateCallbacks},
8};
9
10mod imp {
11 use std::{
12 cell::{Cell, RefCell},
13 marker::PhantomData,
14 };
15
16 use glib::subclass::InitializingObject;
17
18 use super::*;
19
20 #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
21 #[template(resource = "/org/gnome/Fractal/ui/session_view/sidebar/section_row.ui")]
22 #[properties(wrapper_type = super::SidebarSectionRow)]
23 pub struct SidebarSectionRow {
24 #[template_child]
25 pub(super) display_name: TemplateChild<gtk::Label>,
26 #[template_child]
27 notification_count: TemplateChild<gtk::Label>,
28 #[property(get, set = Self::set_section, explicit_notify, nullable)]
30 section: BoundObject<SidebarSection>,
31 section_binding: RefCell<Option<glib::Binding>>,
32 #[property(get, set = Self::set_is_expanded, explicit_notify, construct, default = true)]
34 is_expanded: Cell<bool>,
35 #[property(get = Self::label)]
37 label: PhantomData<Option<String>>,
38 show_label_for_room_category: Cell<Option<RoomCategory>>,
44 }
45
46 #[glib::object_subclass]
47 impl ObjectSubclass for SidebarSectionRow {
48 const NAME: &'static str = "SidebarSectionRow";
49 type Type = super::SidebarSectionRow;
50 type ParentType = adw::Bin;
51
52 fn class_init(klass: &mut Self::Class) {
53 Self::bind_template(klass);
54 Self::bind_template_callbacks(klass);
55 TemplateCallbacks::bind_template_callbacks(klass);
56
57 klass.set_css_name("sidebar-section");
58 }
59
60 fn instance_init(obj: &InitializingObject<Self>) {
61 obj.init_template();
62 }
63 }
64
65 #[glib::derived_properties]
66 impl ObjectImpl for SidebarSectionRow {
67 fn dispose(&self) {
68 if let Some(binding) = self.section_binding.take() {
69 binding.unbind();
70 }
71 }
72 }
73
74 impl WidgetImpl for SidebarSectionRow {}
75 impl BinImpl for SidebarSectionRow {}
76
77 #[gtk::template_callbacks]
78 impl SidebarSectionRow {
79 fn set_section(&self, section: Option<SidebarSection>) {
81 if self.section.obj() == section {
82 return;
83 }
84
85 if let Some(binding) = self.section_binding.take() {
86 binding.unbind();
87 }
88 self.section.disconnect_signals();
89
90 let obj = self.obj();
91
92 if let Some(section) = section {
93 let section_binding = section
94 .bind_property("is-expanded", &*obj, "is-expanded")
95 .sync_create()
96 .build();
97 self.section_binding.replace(Some(section_binding));
98
99 let highlight_handler = section.connect_highlight_notify(clone!(
100 #[weak(rename_to = imp)]
101 self,
102 move |_| {
103 imp.update_highlight();
104 }
105 ));
106
107 self.section.set(section, vec![highlight_handler]);
108 }
109
110 self.update_highlight();
111 obj.notify_section();
112 obj.notify_label();
113 }
114
115 fn label(&self) -> Option<String> {
117 let target_section_name = self.section.obj().as_ref()?.name();
118 let source_room_category = self.show_label_for_room_category.get();
119
120 let label = match source_room_category {
121 Some(RoomCategory::Invited) => match target_section_name {
122 SidebarSectionName::Favorite => gettext("Join Room as Favorite"),
125 SidebarSectionName::Normal => gettext("Join Room"),
126 SidebarSectionName::LowPriority => gettext("Join Room as Low Priority"),
129 SidebarSectionName::Left => gettext("Reject Invite"),
130 _ => target_section_name.to_string(),
131 },
132 Some(RoomCategory::Favorite) => match target_section_name {
133 SidebarSectionName::Normal => gettext("Move to Rooms"),
134 SidebarSectionName::LowPriority => gettext("Move to Low Priority"),
135 SidebarSectionName::Left => gettext("Leave Room"),
136 _ => target_section_name.to_string(),
137 },
138 Some(RoomCategory::Normal) => match target_section_name {
139 SidebarSectionName::Favorite => gettext("Move to Favorites"),
140 SidebarSectionName::LowPriority => gettext("Move to Low Priority"),
141 SidebarSectionName::Left => gettext("Leave Room"),
142 _ => target_section_name.to_string(),
143 },
144 Some(RoomCategory::LowPriority) => match target_section_name {
145 SidebarSectionName::Favorite => gettext("Move to Favorites"),
146 SidebarSectionName::Normal => gettext("Move to Rooms"),
147 SidebarSectionName::Left => gettext("Leave Room"),
148 _ => target_section_name.to_string(),
149 },
150 Some(RoomCategory::Left) => match target_section_name {
151 SidebarSectionName::Favorite => gettext("Rejoin Room as Favorite"),
154 SidebarSectionName::Normal => gettext("Rejoin Room"),
155 SidebarSectionName::LowPriority => gettext("Rejoin Room as Low Priority"),
158 _ => target_section_name.to_string(),
159 },
160 _ => target_section_name.to_string(),
161 };
162
163 Some(label)
164 }
165
166 fn set_is_expanded(&self, is_expanded: bool) {
168 if self.is_expanded.get() == is_expanded {
169 return;
170 }
171 let obj = self.obj();
172
173 if is_expanded {
174 obj.set_state_flags(gtk::StateFlags::CHECKED, false);
175 } else {
176 obj.unset_state_flags(gtk::StateFlags::CHECKED);
177 }
178
179 self.is_expanded.set(is_expanded);
180 self.update_expanded_accessibility_state();
181 self.update_highlight();
182 obj.notify_is_expanded();
183 }
184
185 fn update_highlight(&self) {
188 let highlight = self
189 .section
190 .obj()
191 .as_ref()
192 .map(SidebarSection::highlight)
193 .unwrap_or_default();
194
195 if !self.is_expanded.get() && highlight.contains(HighlightFlags::HIGHLIGHT) {
196 self.notification_count.add_css_class("highlight");
197 } else {
198 self.notification_count.remove_css_class("highlight");
199 }
200 }
201
202 #[template_callback]
204 fn update_expanded_accessibility_state(&self) {
205 if let Some(row) = self.obj().parent() {
206 row.update_state(&[gtk::accessible::State::Expanded(Some(
207 self.is_expanded.get(),
208 ))]);
209 }
210 }
211
212 pub(super) fn set_show_label_for_room_category(&self, category: Option<RoomCategory>) {
214 if self.show_label_for_room_category.get() == category {
215 return;
216 }
217
218 self.show_label_for_room_category.set(category);
219
220 self.obj().notify_label();
221 }
222 }
223}
224
225glib::wrapper! {
226 pub struct SidebarSectionRow(ObjectSubclass<imp::SidebarSectionRow>)
228 @extends gtk::Widget, adw::Bin,
229 @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
230}
231
232impl SidebarSectionRow {
233 pub fn new() -> Self {
234 glib::Object::new()
235 }
236
237 pub(crate) fn set_show_label_for_room_category(&self, category: Option<RoomCategory>) {
239 self.imp().set_show_label_for_room_category(category);
240 }
241
242 pub(crate) fn labelled_by(&self) -> >k::Accessible {
244 self.imp().display_name.upcast_ref()
245 }
246}