Skip to main content

fractal/session/sidebar_data/
item.rs

1use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
2
3use super::{SidebarIconItem, SidebarSection};
4use crate::{
5    session::RoomCategory,
6    utils::{BoundConstructOnlyObject, SingleItemListModel},
7};
8
9mod imp {
10    use std::cell::{Cell, OnceCell};
11
12    use super::*;
13
14    #[derive(Debug, glib::Properties)]
15    #[properties(wrapper_type = super::SidebarItem)]
16    pub struct SidebarItem {
17        /// The item wrapped by this `SidebarItem`.
18        #[property(get, set = Self::set_inner_item, construct_only)]
19        inner_item: BoundConstructOnlyObject<glib::Object>,
20        /// Whether this item is visible.
21        #[property(get)]
22        is_visible: Cell<bool>,
23        /// Whether to inhibit the expanded state.
24        ///
25        /// It means that all the sections will be expanded regardless of
26        /// their "is-expanded" property.
27        #[property(get, set = Self::set_inhibit_expanded, explicit_notify)]
28        inhibit_expanded: Cell<bool>,
29        is_visible_filter: gtk::CustomFilter,
30        is_expanded_filter: gtk::CustomFilter,
31        /// The inner model.
32        model: OnceCell<gtk::FilterListModel>,
33    }
34
35    impl Default for SidebarItem {
36        fn default() -> Self {
37            Self {
38                inner_item: Default::default(),
39                is_visible: Cell::new(true),
40                inhibit_expanded: Default::default(),
41                is_visible_filter: Default::default(),
42                is_expanded_filter: Default::default(),
43                model: Default::default(),
44            }
45        }
46    }
47
48    #[glib::object_subclass]
49    impl ObjectSubclass for SidebarItem {
50        const NAME: &'static str = "SidebarItem";
51        type Type = super::SidebarItem;
52        type Interfaces = (gio::ListModel,);
53    }
54
55    #[glib::derived_properties]
56    impl ObjectImpl for SidebarItem {}
57
58    impl ListModelImpl for SidebarItem {
59        fn item_type(&self) -> glib::Type {
60            glib::Object::static_type()
61        }
62
63        fn n_items(&self) -> u32 {
64            self.model.get().unwrap().n_items()
65        }
66
67        fn item(&self, position: u32) -> Option<glib::Object> {
68            self.model.get().unwrap().item(position)
69        }
70    }
71
72    impl SidebarItem {
73        /// Set the item wrapped by this `SidebarItem`.
74        fn set_inner_item(&self, item: glib::Object) {
75            let mut handlers = Vec::new();
76
77            let inner_model = if let Some(section) = item.downcast_ref::<SidebarSection>() {
78                // Create a list model to have an item for the section itself.
79                let section_model = SingleItemListModel::new(Some(section));
80
81                // Filter the children depending on whether the section is expanded or not.
82                self.is_expanded_filter.set_filter_func(clone!(
83                    #[weak(rename_to = imp)]
84                    self,
85                    #[weak]
86                    section,
87                    #[upgrade_or]
88                    false,
89                    move |_| imp.inhibit_expanded.get() || section.is_expanded()
90                ));
91                let children_model = gtk::FilterListModel::new(
92                    Some(section.clone()),
93                    Some(self.is_expanded_filter.clone()),
94                );
95
96                let is_expanded_handler = section.connect_is_expanded_notify(clone!(
97                    #[weak(rename_to = imp)]
98                    self,
99                    move |_| {
100                        imp.is_expanded_filter.changed(gtk::FilterChange::Different);
101                    }
102                ));
103                handlers.push(is_expanded_handler);
104
105                // Merge the models for the category and its children.
106                let wrapper_model = gio::ListStore::new::<glib::Object>();
107                wrapper_model.append(&section_model);
108                wrapper_model.append(&children_model);
109
110                gtk::FlattenListModel::new(Some(wrapper_model)).upcast::<gio::ListModel>()
111            } else {
112                // Create a list model for the item.
113                SingleItemListModel::new(Some(&item)).upcast()
114            };
115
116            self.inner_item.set(item, handlers);
117
118            self.is_visible_filter.set_filter_func(clone!(
119                #[weak(rename_to = imp)]
120                self,
121                #[upgrade_or]
122                false,
123                move |_| imp.is_visible.get()
124            ));
125            let model =
126                gtk::FilterListModel::new(Some(inner_model), Some(self.is_visible_filter.clone()));
127
128            let obj = self.obj();
129            model.connect_items_changed(clone!(
130                #[weak]
131                obj,
132                move |_model, pos, removed, added| {
133                    obj.items_changed(pos, removed, added);
134                }
135            ));
136
137            self.model.set(model).unwrap();
138        }
139
140        /// Set whether this item is visible.
141        pub(super) fn set_visible(&self, visible: bool) {
142            if self.is_visible.get() == visible {
143                return;
144            }
145
146            self.is_visible.set(visible);
147
148            self.obj().notify_is_visible();
149            self.is_visible_filter.changed(gtk::FilterChange::Different);
150        }
151
152        /// Set whether to inhibit the expanded state.
153        fn set_inhibit_expanded(&self, inhibit: bool) {
154            if self.inhibit_expanded.get() == inhibit {
155                return;
156            }
157
158            self.inhibit_expanded.set(inhibit);
159
160            self.obj().notify_inhibit_expanded();
161            self.is_expanded_filter
162                .changed(gtk::FilterChange::Different);
163        }
164    }
165}
166
167glib::wrapper! {
168    /// A top-level item in the sidebar.
169    ///
170    /// This wraps the inner item to handle its visibility and whether it should
171    /// show its children (i.e. whether it is "expanded").
172    pub struct SidebarItem(ObjectSubclass<imp::SidebarItem>)
173        @implements gio::ListModel;
174}
175
176impl SidebarItem {
177    /// Construct a new `SidebarItem` for the given item.
178    pub fn new(item: impl IsA<glib::Object>) -> Self {
179        glib::Object::builder()
180            .property("inner-item", &item)
181            .build()
182    }
183
184    /// Update the visibility of this item for the drag-n-drop of a room with
185    /// the given category.
186    pub(crate) fn update_visibility_for_room_category(
187        &self,
188        source_category: Option<RoomCategory>,
189    ) {
190        let inner_item = self.inner_item();
191        let visible = if let Some(section) = inner_item.downcast_ref::<SidebarSection>() {
192            section.visible_for_room_category(source_category)
193        } else if let Some(icon_item) = inner_item.downcast_ref::<SidebarIconItem>() {
194            icon_item.visible_for_room_category(source_category)
195        } else {
196            true
197        };
198
199        self.imp().set_visible(visible);
200    }
201}