fractal/components/rows/
loading_row.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use glib::subclass::Signal;
use gtk::{
    glib,
    glib::{clone, closure_local},
    prelude::*,
    subclass::prelude::*,
    CompositeTemplate,
};

use crate::components::LoadingBin;

mod imp {
    use std::{marker::PhantomData, sync::LazyLock};

    use glib::subclass::InitializingObject;

    use super::*;

    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
    #[template(resource = "/org/gnome/Fractal/ui/components/rows/loading_row.ui")]
    #[properties(wrapper_type = super::LoadingRow)]
    pub struct LoadingRow {
        #[template_child]
        pub loading_bin: TemplateChild<LoadingBin>,
        #[template_child]
        pub error_label: TemplateChild<gtk::Label>,
        #[template_child]
        pub retry_button: TemplateChild<gtk::Button>,
        /// The error message to display.
        #[property(get = Self::error, set = Self::set_error, explicit_notify, nullable)]
        pub error: PhantomData<Option<glib::GString>>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for LoadingRow {
        const NAME: &'static str = "LoadingRow";
        type Type = super::LoadingRow;
        type ParentType = gtk::ListBoxRow;

        fn class_init(klass: &mut Self::Class) {
            Self::bind_template(klass);
        }

        fn instance_init(obj: &InitializingObject<Self>) {
            obj.init_template();
        }
    }

    #[glib::derived_properties]
    impl ObjectImpl for LoadingRow {
        fn signals() -> &'static [Signal] {
            static SIGNALS: LazyLock<Vec<Signal>> =
                LazyLock::new(|| vec![Signal::builder("retry").build()]);
            SIGNALS.as_ref()
        }

        fn constructed(&self) {
            self.parent_constructed();
            let obj = self.obj();

            self.retry_button.connect_clicked(clone!(
                #[weak]
                obj,
                move |_| {
                    obj.emit_by_name::<()>("retry", &[]);
                }
            ));
        }
    }

    impl WidgetImpl for LoadingRow {}
    impl ListBoxRowImpl for LoadingRow {}

    impl LoadingRow {
        /// The error message to display.
        fn error(&self) -> Option<glib::GString> {
            let message = self.error_label.text();
            if message.is_empty() {
                None
            } else {
                Some(message)
            }
        }

        /// Set the error message to display.
        ///
        /// If this is `Some`, the error will be shown, otherwise the spinner
        /// will be shown.
        fn set_error(&self, message: Option<&str>) {
            if let Some(message) = message {
                self.error_label.set_text(message);
                self.loading_bin.set_is_loading(false);
            } else {
                self.loading_bin.set_is_loading(true);
            }

            self.obj().notify_error();
        }
    }
}

glib::wrapper! {
    /// A `ListBoxRow` containing a loading spinner.
    ///
    /// It's also possible to set an error once the loading fails, including a retry button.
    pub struct LoadingRow(ObjectSubclass<imp::LoadingRow>)
        @extends gtk::Widget, gtk::ListBoxRow, @implements gtk::Accessible;
}

impl LoadingRow {
    pub fn new() -> Self {
        glib::Object::new()
    }

    /// Connect to the signal emitted when the retry button is clicked.
    pub fn connect_retry<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
        self.connect_closure(
            "retry",
            true,
            closure_local!(move |obj: Self| {
                f(&obj);
            }),
        )
    }
}

impl Default for LoadingRow {
    fn default() -> Self {
        Self::new()
    }
}