fractal/components/loading/
bin.rs

1use adw::prelude::*;
2use gtk::{glib, subclass::prelude::*, CompositeTemplate};
3
4mod imp {
5    use std::marker::PhantomData;
6
7    use glib::subclass::InitializingObject;
8
9    use super::*;
10
11    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
12    #[template(resource = "/org/gnome/Fractal/ui/components/loading/bin.ui")]
13    #[properties(wrapper_type = super::LoadingBin)]
14    pub struct LoadingBin {
15        #[template_child]
16        stack: TemplateChild<gtk::Stack>,
17        #[template_child]
18        child_bin: TemplateChild<adw::Bin>,
19        /// The child widget.
20        #[property(get = Self::child, set = Self::set_child, explicit_notify, nullable)]
21        child: PhantomData<Option<gtk::Widget>>,
22        /// Whether this is showing the spinner.
23        #[property(get = Self::is_loading, set = Self::set_is_loading, explicit_notify)]
24        is_loading: PhantomData<bool>,
25        /// Whether this should keep the same height when showing the spinner or
26        /// the content.
27        #[property(get = Self::vhomogeneous, set = Self::set_vhomogeneous)]
28        vhomogeneous: PhantomData<bool>,
29    }
30
31    #[glib::object_subclass]
32    impl ObjectSubclass for LoadingBin {
33        const NAME: &'static str = "LoadingBin";
34        type Type = super::LoadingBin;
35        type ParentType = gtk::Widget;
36
37        fn class_init(klass: &mut Self::Class) {
38            Self::bind_template(klass);
39
40            klass.set_layout_manager_type::<gtk::BinLayout>();
41            klass.set_css_name("loading-bin");
42        }
43
44        fn instance_init(obj: &InitializingObject<Self>) {
45            obj.init_template();
46        }
47    }
48
49    #[glib::derived_properties]
50    impl ObjectImpl for LoadingBin {
51        fn dispose(&self) {
52            self.stack.unparent();
53        }
54    }
55
56    impl WidgetImpl for LoadingBin {}
57
58    impl LoadingBin {
59        /// Whether this row is showing the spinner.
60        fn is_loading(&self) -> bool {
61            self.stack.visible_child_name().as_deref() == Some("loading")
62        }
63
64        /// Set whether this row is showing the spinner.
65        fn set_is_loading(&self, loading: bool) {
66            if self.is_loading() == loading {
67                return;
68            }
69
70            let child_name = if loading { "loading" } else { "child" };
71            self.stack.set_visible_child_name(child_name);
72            self.obj().notify_is_loading();
73        }
74
75        /// Whether this should keep the same height when showing the spinner or
76        /// the content.
77        fn vhomogeneous(&self) -> bool {
78            self.stack.is_vhomogeneous()
79        }
80
81        /// Set whether this should keep the same height when showing the
82        /// spinner or the content.
83        fn set_vhomogeneous(&self, homogeneous: bool) {
84            self.stack.set_vhomogeneous(homogeneous);
85        }
86
87        /// The child widget.
88        fn child(&self) -> Option<gtk::Widget> {
89            self.child_bin.child()
90        }
91
92        /// Set the child widget.
93        fn set_child(&self, child: Option<&gtk::Widget>) {
94            if self.child().as_ref() == child {
95                return;
96            }
97
98            self.child_bin.set_child(child);
99            self.obj().notify_child();
100        }
101    }
102}
103
104glib::wrapper! {
105    /// A Bin that shows either its child or a loading spinner.
106    pub struct LoadingBin(ObjectSubclass<imp::LoadingBin>)
107        @extends gtk::Widget, @implements gtk::Accessible;
108}
109
110impl LoadingBin {
111    pub fn new() -> Self {
112        glib::Object::new()
113    }
114}
115
116impl Default for LoadingBin {
117    fn default() -> Self {
118        Self::new()
119    }
120}