fractal/components/rows/
loading_button_row.rs

1use adw::{prelude::*, subclass::prelude::*};
2use gtk::{
3    glib,
4    glib::{clone, closure_local},
5};
6
7use crate::components::LoadingBin;
8
9mod imp {
10    use std::{marker::PhantomData, sync::LazyLock};
11
12    use glib::subclass::{InitializingObject, Signal};
13
14    use super::*;
15
16    #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
17    #[template(resource = "/org/gnome/Fractal/ui/components/rows/loading_button_row.ui")]
18    #[properties(wrapper_type = super::LoadingButtonRow)]
19    pub struct LoadingButtonRow {
20        #[template_child]
21        loading_bin: TemplateChild<LoadingBin>,
22        /// Whether the button row is loading.
23        #[property(get = Self::is_loading, set = Self::set_is_loading)]
24        is_loading: PhantomData<bool>,
25    }
26
27    #[glib::object_subclass]
28    impl ObjectSubclass for LoadingButtonRow {
29        const NAME: &'static str = "LoadingButtonRow";
30        type Type = super::LoadingButtonRow;
31        type ParentType = adw::PreferencesRow;
32
33        fn class_init(klass: &mut Self::Class) {
34            Self::bind_template(klass);
35
36            klass.set_css_name("row");
37        }
38
39        fn instance_init(obj: &InitializingObject<Self>) {
40            obj.init_template();
41        }
42    }
43
44    #[glib::derived_properties]
45    impl ObjectImpl for LoadingButtonRow {
46        fn signals() -> &'static [Signal] {
47            static SIGNALS: LazyLock<Vec<Signal>> =
48                LazyLock::new(|| vec![Signal::builder("activated").build()]);
49            SIGNALS.as_ref()
50        }
51
52        fn constructed(&self) {
53            self.parent_constructed();
54
55            self.obj().connect_parent_notify(|obj| {
56                if let Some(listbox) = obj.parent().and_downcast_ref::<gtk::ListBox>() {
57                    listbox.connect_row_activated(clone!(
58                        #[weak]
59                        obj,
60                        move |_, row| {
61                            if *row == obj {
62                                obj.emit_by_name::<()>("activated", &[]);
63                            }
64                        }
65                    ));
66                }
67            });
68        }
69    }
70
71    impl WidgetImpl for LoadingButtonRow {}
72    impl ListBoxRowImpl for LoadingButtonRow {}
73    impl PreferencesRowImpl for LoadingButtonRow {}
74
75    impl LoadingButtonRow {
76        /// Whether the row is loading.
77        fn is_loading(&self) -> bool {
78            self.loading_bin.is_loading()
79        }
80
81        /// Set whether the row is loading.
82        fn set_is_loading(&self, loading: bool) {
83            if self.is_loading() == loading {
84                return;
85            }
86
87            self.loading_bin.set_is_loading(loading);
88
89            let obj = self.obj();
90            obj.set_activatable(!loading);
91            obj.notify_is_loading();
92        }
93    }
94}
95
96glib::wrapper! {
97    /// An `AdwPreferencesRow` usable as a button with a loading state.
98    pub struct LoadingButtonRow(ObjectSubclass<imp::LoadingButtonRow>)
99        @extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow,
100        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Actionable;
101}
102
103impl LoadingButtonRow {
104    pub fn new() -> Self {
105        glib::Object::new()
106    }
107
108    /// Connect to the signal emitted when the row is activated.
109    pub fn connect_activated<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
110        self.connect_closure(
111            "activated",
112            true,
113            closure_local!(move |obj: Self| {
114                f(&obj);
115            }),
116        )
117    }
118}