Skip to main content

fractal/components/loading/
bin.rs

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