fractal/components/power_level_selection/
row.rs

1use adw::{prelude::*, subclass::prelude::*};
2use gtk::{CompositeTemplate, glib};
3
4use super::PowerLevelSelectionPopover;
5use crate::{
6    components::{LoadingBin, RoleBadge},
7    session::model::{Permissions, PowerLevel},
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, CompositeTemplate, glib::Properties)]
21    #[template(resource = "/org/gnome/Fractal/ui/components/power_level_selection/row.ui")]
22    #[properties(wrapper_type = super::PowerLevelSelectionRow)]
23    pub struct PowerLevelSelectionRow {
24        #[template_child]
25        subtitle_bin: TemplateChild<adw::Bin>,
26        #[template_child]
27        combo_selection_bin: TemplateChild<adw::Bin>,
28        #[template_child]
29        arrow_box: TemplateChild<gtk::Box>,
30        #[template_child]
31        loading_bin: TemplateChild<LoadingBin>,
32        #[template_child]
33        popover: TemplateChild<PowerLevelSelectionPopover>,
34        #[template_child]
35        selected_box: TemplateChild<gtk::Box>,
36        #[template_child]
37        selected_level_label: TemplateChild<gtk::Label>,
38        #[template_child]
39        selected_role_badge: TemplateChild<RoleBadge>,
40        /// The permissions to watch.
41        #[property(get, set = Self::set_permissions, explicit_notify, nullable)]
42        permissions: RefCell<Option<Permissions>>,
43        /// The selected power level.
44        #[property(get, set = Self::set_selected_power_level, explicit_notify)]
45        selected_power_level: Cell<PowerLevel>,
46        /// Whether the selected power level should be displayed in the
47        /// subtitle, rather than next to the combo arrow.
48        #[property(get, set = Self::set_use_subtitle, explicit_notify)]
49        use_subtitle: Cell<bool>,
50        /// Whether the row is loading.
51        #[property(get = Self::is_loading, set = Self::set_is_loading)]
52        is_loading: PhantomData<bool>,
53        /// Whether the row is read-only.
54        #[property(get, set = Self::set_read_only, explicit_notify)]
55        read_only: Cell<bool>,
56    }
57
58    #[glib::object_subclass]
59    impl ObjectSubclass for PowerLevelSelectionRow {
60        const NAME: &'static str = "PowerLevelSelectionRow";
61        type Type = super::PowerLevelSelectionRow;
62        type ParentType = adw::PreferencesRow;
63
64        fn class_init(klass: &mut Self::Class) {
65            Self::bind_template(klass);
66            Self::bind_template_callbacks(klass);
67
68            klass.set_accessible_role(gtk::AccessibleRole::ComboBox);
69
70            klass.install_action("power-level-selection-row.popup", None, |obj, _, _| {
71                if !obj.read_only() && !obj.is_loading() {
72                    obj.imp().popover.popup();
73                }
74            });
75        }
76
77        fn instance_init(obj: &InitializingObject<Self>) {
78            obj.init_template();
79        }
80    }
81
82    #[glib::derived_properties]
83    impl ObjectImpl for PowerLevelSelectionRow {
84        fn constructed(&self) {
85            self.parent_constructed();
86
87            self.update_selected_position();
88        }
89    }
90
91    impl WidgetImpl for PowerLevelSelectionRow {}
92    impl ListBoxRowImpl for PowerLevelSelectionRow {}
93    impl PreferencesRowImpl for PowerLevelSelectionRow {}
94
95    #[gtk::template_callbacks]
96    impl PowerLevelSelectionRow {
97        /// Set the permissions to watch.
98        fn set_permissions(&self, permissions: Option<Permissions>) {
99            if *self.permissions.borrow() == permissions {
100                return;
101            }
102
103            self.permissions.replace(permissions);
104            self.update_selected_label();
105            self.obj().notify_permissions();
106        }
107
108        /// Update the label of the selected power level.
109        fn update_selected_label(&self) {
110            let Some(permissions) = self.permissions.borrow().clone() else {
111                return;
112            };
113            let obj = self.obj();
114
115            let power_level = self.selected_power_level.get();
116            let role = permissions.role(power_level);
117
118            self.selected_role_badge.set_role(role);
119            self.selected_level_label
120                .set_label(&power_level.to_string());
121
122            let role_string = format!("{power_level} {role}");
123            obj.update_property(&[gtk::accessible::Property::Description(&role_string)]);
124        }
125
126        /// Set the selected power level.
127        fn set_selected_power_level(&self, power_level: PowerLevel) {
128            if self.selected_power_level.get() == power_level {
129                return;
130            }
131
132            self.selected_power_level.set(power_level);
133
134            self.update_selected_label();
135            self.obj().notify_selected_power_level();
136        }
137
138        /// Set whether the selected power level should be displayed in the
139        /// subtitle, rather than next to the combo arrow.
140        fn set_use_subtitle(&self, use_subtitle: bool) {
141            if self.use_subtitle.get() == use_subtitle {
142                return;
143            }
144
145            self.use_subtitle.set(use_subtitle);
146
147            self.update_selected_position();
148            self.obj().notify_use_subtitle();
149        }
150
151        /// Whether the row is loading.
152        fn is_loading(&self) -> bool {
153            self.loading_bin.is_loading()
154        }
155
156        /// Set whether the row is loading.
157        fn set_is_loading(&self, loading: bool) {
158            if self.is_loading() == loading {
159                return;
160            }
161
162            self.loading_bin.set_is_loading(loading);
163            self.obj().notify_is_loading();
164        }
165
166        /// Update the position of the selected label.
167        fn update_selected_position(&self) {
168            if self.use_subtitle.get() {
169                if self
170                    .selected_box
171                    .parent()
172                    .is_none_or(|p| p != *self.subtitle_bin)
173                {
174                    if self.selected_box.parent().is_some() {
175                        self.combo_selection_bin.set_child(None::<&gtk::Widget>);
176                    }
177
178                    self.subtitle_bin.set_child(Some(&*self.selected_box));
179                }
180            } else if self
181                .selected_box
182                .parent()
183                .is_none_or(|p| p != *self.combo_selection_bin)
184            {
185                if self.selected_box.parent().is_some() {
186                    self.subtitle_bin.set_child(None::<&gtk::Widget>);
187                }
188
189                self.combo_selection_bin
190                    .set_child(Some(&*self.selected_box));
191            }
192        }
193
194        /// Set whether the row is read-only.
195        fn set_read_only(&self, read_only: bool) {
196            if self.read_only.get() == read_only {
197                return;
198            }
199            let obj = self.obj();
200
201            self.read_only.set(read_only);
202
203            obj.update_property(&[gtk::accessible::Property::ReadOnly(read_only)]);
204            obj.notify_read_only();
205        }
206
207        /// The popover's visibility changed.
208        #[template_callback]
209        fn popover_visible(&self) {
210            let obj = self.obj();
211            let is_visible = self.popover.is_visible();
212
213            if is_visible {
214                obj.add_css_class("has-open-popup");
215            } else {
216                obj.remove_css_class("has-open-popup");
217            }
218        }
219    }
220}
221
222glib::wrapper! {
223    /// An `AdwPreferencesRow` behaving like a combo box to select a room member's power level.
224    pub struct PowerLevelSelectionRow(ObjectSubclass<imp::PowerLevelSelectionRow>)
225        @extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow,
226        @implements gtk::Actionable, gtk::Accessible;
227}
228
229impl PowerLevelSelectionRow {
230    pub fn new() -> Self {
231        glib::Object::new()
232    }
233}