fractal/components/power_level_selection/
row.rs1use adw::{prelude::*, subclass::prelude::*};
2use gtk::{glib, glib::closure_local};
3use ruma::{Int, events::room::power_levels::UserPowerLevel, int};
4
5use super::PowerLevelSelectionPopover;
6use crate::{
7 components::{LoadingBin, RoleBadge},
8 session::Permissions,
9};
10
11mod imp {
12 use std::{
13 cell::{Cell, RefCell},
14 marker::PhantomData,
15 sync::LazyLock,
16 };
17
18 use glib::subclass::{InitializingObject, Signal};
19
20 use super::*;
21
22 #[derive(Debug, gtk::CompositeTemplate, glib::Properties)]
23 #[template(resource = "/org/gnome/Fractal/ui/components/power_level_selection/row.ui")]
24 #[properties(wrapper_type = super::PowerLevelSelectionRow)]
25 pub struct PowerLevelSelectionRow {
26 #[template_child]
27 subtitle_bin: TemplateChild<adw::Bin>,
28 #[template_child]
29 combo_selection_bin: TemplateChild<adw::Bin>,
30 #[template_child]
31 arrow_box: TemplateChild<gtk::Box>,
32 #[template_child]
33 loading_bin: TemplateChild<LoadingBin>,
34 #[template_child]
35 popover: TemplateChild<PowerLevelSelectionPopover>,
36 #[template_child]
37 selected_box: TemplateChild<gtk::Box>,
38 #[template_child]
39 selected_level_label: TemplateChild<gtk::Label>,
40 #[template_child]
41 creator_info_button: TemplateChild<gtk::MenuButton>,
42 #[template_child]
43 selected_role_badge: TemplateChild<RoleBadge>,
44 #[property(get, set = Self::set_permissions, explicit_notify, nullable)]
46 permissions: RefCell<Option<Permissions>>,
47 pub(super) selected_power_level: Cell<UserPowerLevel>,
49 #[property(get, set = Self::set_use_subtitle, explicit_notify)]
52 use_subtitle: Cell<bool>,
53 #[property(get = Self::is_loading, set = Self::set_is_loading)]
55 is_loading: PhantomData<bool>,
56 #[property(get, set = Self::set_read_only, explicit_notify)]
58 read_only: Cell<bool>,
59 }
60
61 impl Default for PowerLevelSelectionRow {
62 fn default() -> Self {
63 Self {
64 subtitle_bin: Default::default(),
65 combo_selection_bin: Default::default(),
66 arrow_box: Default::default(),
67 loading_bin: Default::default(),
68 popover: Default::default(),
69 selected_box: Default::default(),
70 selected_level_label: Default::default(),
71 creator_info_button: Default::default(),
72 selected_role_badge: Default::default(),
73 permissions: Default::default(),
74 selected_power_level: Cell::new(UserPowerLevel::Int(int!(0))),
75 use_subtitle: Default::default(),
76 is_loading: PhantomData,
77 read_only: Default::default(),
78 }
79 }
80 }
81
82 #[glib::object_subclass]
83 impl ObjectSubclass for PowerLevelSelectionRow {
84 const NAME: &'static str = "PowerLevelSelectionRow";
85 type Type = super::PowerLevelSelectionRow;
86 type ParentType = adw::PreferencesRow;
87
88 fn class_init(klass: &mut Self::Class) {
89 Self::bind_template(klass);
90 Self::bind_template_callbacks(klass);
91
92 klass.set_accessible_role(gtk::AccessibleRole::ComboBox);
93
94 klass.install_action("power-level-selection-row.popup", None, |obj, _, _| {
95 if !obj.read_only() && !obj.is_loading() {
96 obj.imp().popover.popup();
97 }
98 });
99 }
100
101 fn instance_init(obj: &InitializingObject<Self>) {
102 obj.init_template();
103 }
104 }
105
106 #[glib::derived_properties]
107 impl ObjectImpl for PowerLevelSelectionRow {
108 fn signals() -> &'static [Signal] {
109 static SIGNALS: LazyLock<Vec<Signal>> =
110 LazyLock::new(|| vec![Signal::builder("selected-power-level-changed").build()]);
111 SIGNALS.as_ref()
112 }
113
114 fn constructed(&self) {
115 self.parent_constructed();
116
117 self.update_selected_position();
118 }
119 }
120
121 impl WidgetImpl for PowerLevelSelectionRow {}
122 impl ListBoxRowImpl for PowerLevelSelectionRow {}
123 impl PreferencesRowImpl for PowerLevelSelectionRow {}
124
125 #[gtk::template_callbacks]
126 impl PowerLevelSelectionRow {
127 fn set_permissions(&self, permissions: Option<Permissions>) {
129 if *self.permissions.borrow() == permissions {
130 return;
131 }
132
133 self.permissions.replace(permissions);
134 self.update_selected_label();
135 self.obj().notify_permissions();
136 }
137
138 fn update_selected_label(&self) {
140 let Some(permissions) = self.permissions.borrow().clone() else {
141 return;
142 };
143
144 let power_level = self.selected_power_level.get();
145 let role = permissions.role(power_level);
146
147 self.selected_role_badge.set_role(role);
148
149 let (creator_info_visible, accessible_desc) =
150 if let UserPowerLevel::Int(value) = power_level {
151 self.selected_level_label.set_label(&value.to_string());
152 self.popover.set_selected_power_level(i64::from(value));
153 (false, format!("{value} {role}"))
154 } else {
155 (true, role.to_string())
156 };
157
158 self.creator_info_button.set_visible(creator_info_visible);
159 self.selected_level_label.set_visible(!creator_info_visible);
160
161 self.obj()
162 .update_property(&[gtk::accessible::Property::Description(&accessible_desc)]);
163 }
164
165 pub(super) fn set_selected_power_level(&self, power_level: UserPowerLevel) {
167 if self.selected_power_level.get() == power_level {
168 return;
169 }
170
171 self.selected_power_level.set(power_level);
172
173 self.update_selected_label();
174 self.obj()
175 .emit_by_name::<()>("selected-power-level-changed", &[]);
176 }
177
178 fn set_use_subtitle(&self, use_subtitle: bool) {
181 if self.use_subtitle.get() == use_subtitle {
182 return;
183 }
184
185 self.use_subtitle.set(use_subtitle);
186
187 self.update_selected_position();
188 self.obj().notify_use_subtitle();
189 }
190
191 fn is_loading(&self) -> bool {
193 self.loading_bin.is_loading()
194 }
195
196 fn set_is_loading(&self, loading: bool) {
198 if self.is_loading() == loading {
199 return;
200 }
201
202 self.loading_bin.set_is_loading(loading);
203 self.obj().notify_is_loading();
204 }
205
206 fn update_selected_position(&self) {
208 if self.use_subtitle.get() {
209 if self
210 .selected_box
211 .parent()
212 .is_none_or(|p| p != *self.subtitle_bin)
213 {
214 if self.selected_box.parent().is_some() {
215 self.combo_selection_bin.set_child(None::<>k::Widget>);
216 }
217
218 self.subtitle_bin.set_child(Some(&*self.selected_box));
219 }
220 } else if self
221 .selected_box
222 .parent()
223 .is_none_or(|p| p != *self.combo_selection_bin)
224 {
225 if self.selected_box.parent().is_some() {
226 self.subtitle_bin.set_child(None::<>k::Widget>);
227 }
228
229 self.combo_selection_bin
230 .set_child(Some(&*self.selected_box));
231 }
232 }
233
234 fn set_read_only(&self, read_only: bool) {
236 if self.read_only.get() == read_only {
237 return;
238 }
239 let obj = self.obj();
240
241 self.read_only.set(read_only);
242
243 obj.update_property(&[gtk::accessible::Property::ReadOnly(read_only)]);
244 obj.notify_read_only();
245 }
246
247 #[template_callback]
249 fn popover_visible(&self) {
250 let obj = self.obj();
251 let is_visible = self.popover.is_visible();
252
253 if is_visible {
254 obj.add_css_class("has-open-popup");
255 } else {
256 obj.remove_css_class("has-open-popup");
257 }
258 }
259
260 #[template_callback]
262 fn power_level_changed(&self) {
263 self.set_selected_power_level(UserPowerLevel::Int(Int::new_saturating(
264 self.popover.selected_power_level(),
265 )));
266 }
267 }
268}
269
270glib::wrapper! {
271 pub struct PowerLevelSelectionRow(ObjectSubclass<imp::PowerLevelSelectionRow>)
273 @extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow,
274 @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Actionable;
275}
276
277impl PowerLevelSelectionRow {
278 pub fn new() -> Self {
279 glib::Object::new()
280 }
281
282 pub(crate) fn selected_power_level(&self) -> UserPowerLevel {
284 self.imp().selected_power_level.get()
285 }
286
287 pub(crate) fn set_selected_power_level(&self, power_level: UserPowerLevel) {
289 self.imp().set_selected_power_level(power_level);
290 }
291
292 pub fn connect_power_level_changed<F: Fn(&Self) + 'static>(
294 &self,
295 f: F,
296 ) -> glib::SignalHandlerId {
297 self.connect_closure(
298 "selected-power-level-changed",
299 true,
300 closure_local!(move |obj: Self| {
301 f(&obj);
302 }),
303 )
304 }
305}