fractal/components/power_level_selection/
popover.rs

1use adw::{prelude::*, subclass::prelude::*};
2use gtk::{glib, glib::clone, CompositeTemplate};
3
4use crate::{
5    session::model::{Permissions, PowerLevel, POWER_LEVEL_ADMIN, POWER_LEVEL_MOD},
6    utils::BoundObject,
7};
8
9mod imp {
10    use std::cell::Cell;
11
12    use glib::subclass::InitializingObject;
13
14    use super::*;
15
16    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
17    #[template(resource = "/org/gnome/Fractal/ui/components/power_level_selection/popover.ui")]
18    #[properties(wrapper_type = super::PowerLevelSelectionPopover)]
19    pub struct PowerLevelSelectionPopover {
20        #[template_child]
21        admin_row: TemplateChild<gtk::ListBoxRow>,
22        #[template_child]
23        admin_selected: TemplateChild<gtk::Image>,
24        #[template_child]
25        mod_row: TemplateChild<gtk::ListBoxRow>,
26        #[template_child]
27        mod_selected: TemplateChild<gtk::Image>,
28        #[template_child]
29        default_row: TemplateChild<gtk::ListBoxRow>,
30        #[template_child]
31        default_pl_label: TemplateChild<gtk::Label>,
32        #[template_child]
33        default_selected: TemplateChild<gtk::Image>,
34        #[template_child]
35        muted_row: TemplateChild<gtk::ListBoxRow>,
36        #[template_child]
37        muted_pl_label: TemplateChild<gtk::Label>,
38        #[template_child]
39        muted_selected: TemplateChild<gtk::Image>,
40        #[template_child]
41        custom_row: TemplateChild<adw::SpinRow>,
42        #[template_child]
43        custom_adjustment: TemplateChild<gtk::Adjustment>,
44        #[template_child]
45        custom_confirm: TemplateChild<gtk::Button>,
46        /// The permissions to watch.
47        #[property(get, set = Self::set_permissions, explicit_notify, nullable)]
48        permissions: BoundObject<Permissions>,
49        /// The selected power level.
50        #[property(get, set = Self::set_selected_power_level, explicit_notify)]
51        selected_power_level: Cell<PowerLevel>,
52    }
53
54    #[glib::object_subclass]
55    impl ObjectSubclass for PowerLevelSelectionPopover {
56        const NAME: &'static str = "PowerLevelSelectionPopover";
57        type Type = super::PowerLevelSelectionPopover;
58        type ParentType = gtk::Popover;
59
60        fn class_init(klass: &mut Self::Class) {
61            Self::bind_template(klass);
62            Self::bind_template_callbacks(klass);
63        }
64
65        fn instance_init(obj: &InitializingObject<Self>) {
66            obj.init_template();
67        }
68    }
69
70    #[glib::derived_properties]
71    impl ObjectImpl for PowerLevelSelectionPopover {}
72
73    impl WidgetImpl for PowerLevelSelectionPopover {}
74    impl PopoverImpl for PowerLevelSelectionPopover {}
75
76    #[gtk::template_callbacks]
77    impl PowerLevelSelectionPopover {
78        /// Set the permissions to watch.
79        fn set_permissions(&self, permissions: Option<Permissions>) {
80            if self.permissions.obj() == permissions {
81                return;
82            }
83
84            self.permissions.disconnect_signals();
85
86            if let Some(permissions) = permissions {
87                let own_pl_handler = permissions.connect_own_power_level_notify(clone!(
88                    #[weak(rename_to = imp)]
89                    self,
90                    move |_| {
91                        imp.update();
92                    }
93                ));
94                let default_pl_handler = permissions.connect_default_power_level_notify(clone!(
95                    #[weak(rename_to = imp)]
96                    self,
97                    move |_| {
98                        imp.update_default();
99                        imp.update_muted();
100                        imp.update_selection();
101                    }
102                ));
103                let muted_pl_handler = permissions.connect_mute_power_level_notify(clone!(
104                    #[weak(rename_to = imp)]
105                    self,
106                    move |_| {
107                        imp.update_muted();
108                        imp.update_selection();
109                    }
110                ));
111
112                self.permissions.set(
113                    permissions,
114                    vec![own_pl_handler, default_pl_handler, muted_pl_handler],
115                );
116            }
117
118            self.update();
119            self.obj().notify_permissions();
120        }
121
122        /// Set the selected power level.
123        fn set_selected_power_level(&self, power_level: PowerLevel) {
124            if self.selected_power_level.get() == power_level {
125                return;
126            }
127
128            self.selected_power_level.set(power_level);
129
130            self.update_selection();
131            self.update_custom();
132            self.obj().notify_selected_power_level();
133        }
134
135        /// Update the rows.
136        fn update(&self) {
137            self.update_admin();
138            self.update_mod();
139            self.update_default();
140            self.update_muted();
141            self.update_custom();
142            self.update_selection();
143        }
144
145        /// Update the admin row.
146        fn update_admin(&self) {
147            let Some(permissions) = self.permissions.obj() else {
148                return;
149            };
150
151            let can_change_to_admin = permissions.own_power_level() >= POWER_LEVEL_ADMIN;
152
153            if can_change_to_admin {
154                self.admin_row.set_sensitive(true);
155                self.admin_row.set_activatable(true);
156            } else {
157                self.admin_row.set_sensitive(false);
158                self.admin_row.set_activatable(false);
159            }
160        }
161
162        /// Update the moderator row.
163        fn update_mod(&self) {
164            let Some(permissions) = self.permissions.obj() else {
165                return;
166            };
167
168            let can_change_to_mod = permissions.own_power_level() >= POWER_LEVEL_MOD;
169
170            if can_change_to_mod {
171                self.mod_row.set_sensitive(true);
172                self.mod_row.set_activatable(true);
173            } else {
174                self.mod_row.set_sensitive(false);
175                self.mod_row.set_activatable(false);
176            }
177        }
178
179        /// Update the default row.
180        fn update_default(&self) {
181            let Some(permissions) = self.permissions.obj() else {
182                return;
183            };
184
185            let default = permissions.default_power_level();
186            self.default_pl_label.set_label(&default.to_string());
187
188            let can_change_to_default = permissions.own_power_level() >= default;
189
190            if can_change_to_default {
191                self.default_row.set_sensitive(true);
192                self.default_row.set_activatable(true);
193            } else {
194                self.default_row.set_sensitive(false);
195                self.default_row.set_activatable(false);
196            }
197        }
198
199        /// Update the muted row.
200        fn update_muted(&self) {
201            let Some(permissions) = self.permissions.obj() else {
202                return;
203            };
204
205            let mute = permissions.mute_power_level();
206            let default = permissions.default_power_level();
207
208            if mute >= default {
209                // There is no point in having the muted row since all users are muted by
210                // default.
211                self.muted_row.set_visible(false);
212                return;
213            }
214
215            self.muted_pl_label.set_label(&mute.to_string());
216
217            let can_change_to_muted = permissions.own_power_level() >= mute;
218
219            if can_change_to_muted {
220                self.muted_row.set_sensitive(true);
221                self.muted_row.set_activatable(true);
222            } else {
223                self.muted_row.set_sensitive(false);
224                self.muted_row.set_activatable(false);
225            }
226
227            self.muted_row.set_visible(true);
228        }
229
230        /// Update the custom row.
231        fn update_custom(&self) {
232            let Some(permissions) = self.permissions.obj() else {
233                return;
234            };
235
236            self.custom_adjustment
237                .set_upper(permissions.own_power_level() as f64);
238            self.custom_adjustment
239                .set_value(self.selected_power_level.get() as f64);
240        }
241
242        /// Update the selected row.
243        fn update_selection(&self) {
244            let Some(permissions) = self.permissions.obj() else {
245                return;
246            };
247
248            let power_level = self.selected_power_level.get();
249
250            self.admin_selected
251                .set_opacity((power_level == POWER_LEVEL_ADMIN).into());
252            self.mod_selected
253                .set_opacity((power_level == POWER_LEVEL_MOD).into());
254            self.default_selected
255                .set_opacity((power_level == permissions.default_power_level()).into());
256            self.muted_selected
257                .set_opacity((power_level == permissions.mute_power_level()).into());
258        }
259
260        /// The custom value changed.
261        #[template_callback]
262        fn custom_value_changed(&self) {
263            let power_level = self.custom_adjustment.value() as PowerLevel;
264            let can_confirm = power_level != self.selected_power_level.get();
265
266            self.custom_confirm.set_sensitive(can_confirm);
267        }
268
269        /// The custom value was confirmed.
270        #[template_callback]
271        fn custom_value_confirmed(&self) {
272            let power_level = self.custom_adjustment.value() as PowerLevel;
273
274            self.obj().popdown();
275            self.set_selected_power_level(power_level);
276        }
277
278        /// A row was activated.
279        #[template_callback]
280        fn row_activated(&self, row: &gtk::ListBoxRow) {
281            let Some(permissions) = self.permissions.obj() else {
282                return;
283            };
284
285            let power_level = match row.index() {
286                0 => POWER_LEVEL_ADMIN,
287                1 => POWER_LEVEL_MOD,
288                2 => permissions.default_power_level(),
289                3 => permissions.mute_power_level(),
290                _ => return,
291            };
292
293            self.obj().popdown();
294            self.set_selected_power_level(power_level);
295        }
296    }
297}
298
299glib::wrapper! {
300    /// A popover to select a room member's power level.
301    pub struct PowerLevelSelectionPopover(ObjectSubclass<imp::PowerLevelSelectionPopover>)
302        @extends gtk::Widget, gtk::Popover, @implements gtk::Accessible;
303}
304
305impl PowerLevelSelectionPopover {
306    pub fn new() -> Self {
307        glib::Object::new()
308    }
309}