Skip to main content

fractal/components/rows/
loading_row.rs

1use gtk::{
2    glib,
3    glib::{clone, closure_local},
4    prelude::*,
5    subclass::prelude::*,
6};
7
8use crate::components::LoadingBin;
9
10mod imp {
11    use std::{marker::PhantomData, sync::LazyLock};
12
13    use glib::subclass::{InitializingObject, Signal};
14
15    use super::*;
16
17    #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
18    #[template(resource = "/org/gnome/Fractal/ui/components/rows/loading_row.ui")]
19    #[properties(wrapper_type = super::LoadingRow)]
20    pub struct LoadingRow {
21        #[template_child]
22        loading_bin: TemplateChild<LoadingBin>,
23        #[template_child]
24        error_label: TemplateChild<gtk::Label>,
25        #[template_child]
26        retry_button: TemplateChild<gtk::Button>,
27        /// The error message to display.
28        #[property(get = Self::error, set = Self::set_error, explicit_notify, nullable)]
29        error: PhantomData<Option<glib::GString>>,
30    }
31
32    #[glib::object_subclass]
33    impl ObjectSubclass for LoadingRow {
34        const NAME: &'static str = "LoadingRow";
35        type Type = super::LoadingRow;
36        type ParentType = gtk::ListBoxRow;
37
38        fn class_init(klass: &mut Self::Class) {
39            Self::bind_template(klass);
40        }
41
42        fn instance_init(obj: &InitializingObject<Self>) {
43            obj.init_template();
44        }
45    }
46
47    #[glib::derived_properties]
48    impl ObjectImpl for LoadingRow {
49        fn signals() -> &'static [Signal] {
50            static SIGNALS: LazyLock<Vec<Signal>> =
51                LazyLock::new(|| vec![Signal::builder("retry").build()]);
52            SIGNALS.as_ref()
53        }
54
55        fn constructed(&self) {
56            self.parent_constructed();
57            let obj = self.obj();
58
59            self.retry_button.connect_clicked(clone!(
60                #[weak]
61                obj,
62                move |_| {
63                    obj.emit_by_name::<()>("retry", &[]);
64                }
65            ));
66        }
67    }
68
69    impl WidgetImpl for LoadingRow {}
70    impl ListBoxRowImpl for LoadingRow {}
71
72    impl LoadingRow {
73        /// The error message to display.
74        fn error(&self) -> Option<glib::GString> {
75            let message = self.error_label.text();
76            if message.is_empty() {
77                None
78            } else {
79                Some(message)
80            }
81        }
82
83        /// Set the error message to display.
84        ///
85        /// If this is `Some`, the error will be shown, otherwise the spinner
86        /// will be shown.
87        fn set_error(&self, message: Option<&str>) {
88            if let Some(message) = message {
89                self.error_label.set_text(message);
90                self.loading_bin.set_is_loading(false);
91            } else {
92                self.loading_bin.set_is_loading(true);
93            }
94
95            self.obj().notify_error();
96        }
97    }
98}
99
100glib::wrapper! {
101    /// A `ListBoxRow` containing a loading spinner.
102    ///
103    /// It's also possible to set an error once the loading fails, including a retry button.
104    pub struct LoadingRow(ObjectSubclass<imp::LoadingRow>)
105        @extends gtk::Widget, gtk::ListBoxRow,
106        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Actionable;
107}
108
109impl LoadingRow {
110    pub fn new() -> Self {
111        glib::Object::new()
112    }
113
114    /// Connect to the signal emitted when the retry button is clicked.
115    pub fn connect_retry<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
116        self.connect_closure(
117            "retry",
118            true,
119            closure_local!(move |obj: Self| {
120                f(&obj);
121            }),
122        )
123    }
124}
125
126impl Default for LoadingRow {
127    fn default() -> Self {
128        Self::new()
129    }
130}