fractal/components/power_level_selection/
row.rs1use 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 #[property(get, set = Self::set_permissions, explicit_notify, nullable)]
42 permissions: RefCell<Option<Permissions>>,
43 #[property(get, set = Self::set_selected_power_level, explicit_notify)]
45 selected_power_level: Cell<PowerLevel>,
46 #[property(get, set = Self::set_use_subtitle, explicit_notify)]
49 use_subtitle: Cell<bool>,
50 #[property(get = Self::is_loading, set = Self::set_is_loading)]
52 is_loading: PhantomData<bool>,
53 #[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 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 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 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 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 fn is_loading(&self) -> bool {
153 self.loading_bin.is_loading()
154 }
155
156 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 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::<>k::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::<>k::Widget>);
187 }
188
189 self.combo_selection_bin
190 .set_child(Some(&*self.selected_box));
191 }
192 }
193
194 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 #[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 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}