fractal/components/rows/
removable_row.rs

1use adw::{prelude::*, subclass::prelude::*};
2use gtk::{glib, glib::closure_local, CompositeTemplate};
3
4use crate::components::LoadingButton;
5
6mod imp {
7    use std::{cell::RefCell, marker::PhantomData, sync::LazyLock};
8
9    use glib::subclass::{InitializingObject, Signal};
10
11    use super::*;
12
13    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
14    #[template(resource = "/org/gnome/Fractal/ui/components/rows/removable_row.ui")]
15    #[properties(wrapper_type = super::RemovableRow)]
16    pub struct RemovableRow {
17        #[template_child]
18        remove_button: TemplateChild<LoadingButton>,
19        #[template_child]
20        extra_suffix_bin: TemplateChild<adw::Bin>,
21        /// The tooltip text of the remove button.
22        #[property(get = Self::remove_button_tooltip_text, set = Self::set_remove_button_tooltip_text, explicit_notify, nullable)]
23        remove_button_tooltip_text: PhantomData<Option<glib::GString>>,
24        /// The accessible label of the remove button.
25        #[property(get, set = Self::set_remove_button_accessible_label, explicit_notify, nullable)]
26        remove_button_accessible_label: RefCell<Option<String>>,
27        /// Whether this row is loading.
28        #[property(get = Self::is_loading, set = Self::set_is_loading, explicit_notify)]
29        is_loading: PhantomData<bool>,
30        /// The extra suffix widget of this row.
31        ///
32        /// The widget is placed before the remove button.
33        #[property(get = Self::extra_suffix, set = Self::set_extra_suffix, explicit_notify, nullable)]
34        extra_suffix: PhantomData<Option<gtk::Widget>>,
35    }
36
37    #[glib::object_subclass]
38    impl ObjectSubclass for RemovableRow {
39        const NAME: &'static str = "RemovableRow";
40        type Type = super::RemovableRow;
41        type ParentType = adw::ActionRow;
42
43        fn class_init(klass: &mut Self::Class) {
44            Self::bind_template(klass);
45            Self::bind_template_callbacks(klass);
46        }
47
48        fn instance_init(obj: &InitializingObject<Self>) {
49            obj.init_template();
50        }
51    }
52
53    #[glib::derived_properties]
54    impl ObjectImpl for RemovableRow {
55        fn signals() -> &'static [Signal] {
56            static SIGNALS: LazyLock<Vec<Signal>> =
57                LazyLock::new(|| vec![Signal::builder("remove").build()]);
58            SIGNALS.as_ref()
59        }
60    }
61
62    impl WidgetImpl for RemovableRow {}
63    impl ListBoxRowImpl for RemovableRow {}
64    impl PreferencesRowImpl for RemovableRow {}
65    impl ActionRowImpl for RemovableRow {}
66
67    #[gtk::template_callbacks]
68    impl RemovableRow {
69        /// The tooltip text of the remove button.
70        fn remove_button_tooltip_text(&self) -> Option<glib::GString> {
71            self.remove_button.tooltip_text()
72        }
73
74        /// Set the tooltip text of the remove button.
75        fn set_remove_button_tooltip_text(&self, tooltip_text: Option<&str>) {
76            if self.remove_button_tooltip_text().as_deref() == tooltip_text {
77                return;
78            }
79
80            self.remove_button.set_tooltip_text(tooltip_text);
81            self.obj().notify_remove_button_tooltip_text();
82        }
83
84        /// Set the accessible label of the remove button.
85        fn set_remove_button_accessible_label(&self, label: Option<String>) {
86            if *self.remove_button_accessible_label.borrow() == label {
87                return;
88            }
89
90            if let Some(label) = &label {
91                self.remove_button
92                    .update_property(&[gtk::accessible::Property::Label(label)]);
93            } else {
94                self.remove_button
95                    .reset_property(gtk::AccessibleProperty::Label);
96            }
97
98            self.remove_button_accessible_label.replace(label);
99            self.obj().notify_remove_button_accessible_label();
100        }
101
102        /// Whether this row is loading.
103        fn is_loading(&self) -> bool {
104            self.remove_button.is_loading()
105        }
106
107        /// Set whether this row is loading.
108        fn set_is_loading(&self, is_loading: bool) {
109            if self.is_loading() == is_loading {
110                return;
111            }
112
113            self.remove_button.set_is_loading(is_loading);
114
115            let obj = self.obj();
116            obj.set_sensitive(!is_loading);
117            obj.notify_is_loading();
118        }
119
120        /// The extra suffix widget of this row.
121        fn extra_suffix(&self) -> Option<gtk::Widget> {
122            self.extra_suffix_bin.child()
123        }
124
125        /// Set the extra suffix widget of this row.
126        fn set_extra_suffix(&self, widget: Option<&gtk::Widget>) {
127            if self.extra_suffix().as_ref() == widget {
128                return;
129            }
130
131            self.extra_suffix_bin.set_child(widget);
132            self.obj().notify_extra_suffix();
133        }
134
135        /// Emit the `remove` signal.
136        #[template_callback]
137        fn remove(&self) {
138            self.obj().emit_by_name::<()>("remove", &[]);
139        }
140    }
141}
142
143glib::wrapper! {
144    /// An `AdwActionRow` with a "remove" button.
145    pub struct RemovableRow(ObjectSubclass<imp::RemovableRow>)
146        @extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::ActionRow,
147        @implements gtk::Actionable, gtk::Accessible;
148}
149
150impl RemovableRow {
151    pub fn new() -> Self {
152        glib::Object::new()
153    }
154
155    /// Connect to the `remove` signal.
156    pub fn connect_remove<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
157        self.connect_closure(
158            "remove",
159            true,
160            closure_local!(move |obj: Self| {
161                f(&obj);
162            }),
163        )
164    }
165}