Skip to main content

fractal/utils/
expression_list_model.rs

1use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
2use indexmap::IndexMap;
3use tracing::error;
4
5use crate::utils::BoundObject;
6
7mod imp {
8    use std::cell::RefCell;
9
10    use super::*;
11
12    #[derive(Debug, Default, glib::Properties)]
13    #[properties(wrapper_type = super::ExpressionListModel)]
14    pub struct ExpressionListModel {
15        #[property(get, set = Self::set_model, explicit_notify, nullable)]
16        model: BoundObject<gio::ListModel>,
17        expressions: RefCell<Vec<gtk::Expression>>,
18        /// Tracked items with their expression watches, kept in sync with
19        /// the underlying model's positions. Supports O(1) lookup by item.
20        watches: RefCell<IndexMap<glib::Object, Vec<gtk::ExpressionWatch>>>,
21    }
22
23    #[glib::object_subclass]
24    impl ObjectSubclass for ExpressionListModel {
25        const NAME: &'static str = "ExpressionListModel";
26        type Type = super::ExpressionListModel;
27        type Interfaces = (gio::ListModel,);
28    }
29
30    #[glib::derived_properties]
31    impl ObjectImpl for ExpressionListModel {
32        fn dispose(&self) {
33            for watch in self.watches.take().into_values().flatten() {
34                watch.unwatch();
35            }
36        }
37    }
38
39    impl ListModelImpl for ExpressionListModel {
40        fn item_type(&self) -> glib::Type {
41            self.model
42                .obj()
43                .map_or_else(glib::Object::static_type, |m| m.item_type())
44        }
45
46        fn n_items(&self) -> u32 {
47            self.model.obj().map(|m| m.n_items()).unwrap_or_default()
48        }
49
50        fn item(&self, position: u32) -> Option<glib::Object> {
51            self.model.obj().and_then(|m| m.item(position))
52        }
53    }
54
55    impl ExpressionListModel {
56        /// Set the underlying model.
57        fn set_model(&self, model: Option<gio::ListModel>) {
58            if self.model.obj() == model {
59                return;
60            }
61
62            let obj = self.obj();
63            let removed = self.n_items();
64
65            self.model.disconnect_signals();
66            for watch in self.watches.take().into_values().flatten() {
67                watch.unwatch();
68            }
69
70            let added = if let Some(model) = model {
71                let items_changed_handler = model.connect_items_changed(clone!(
72                    #[strong]
73                    obj,
74                    move |_, pos, removed, added| {
75                        obj.imp().watch_items(pos, removed, added);
76                        obj.items_changed(pos, removed, added);
77                    }
78                ));
79
80                let added = model.n_items();
81                self.model.set(model, vec![items_changed_handler]);
82
83                self.watch_items(0, removed, added);
84                added
85            } else {
86                0
87            };
88
89            let obj = self.obj();
90            obj.items_changed(0, removed, added);
91            obj.notify_model();
92        }
93
94        /// Set the expressions to watch.
95        pub(super) fn set_expressions(&self, expressions: Vec<gtk::Expression>) {
96            for watch in self.watches.take().into_values().flatten() {
97                watch.unwatch();
98            }
99
100            self.expressions.replace(expressions);
101
102            let n_items = self.n_items();
103            self.watch_items(0, n_items, n_items);
104        }
105
106        /// Watch and unwatch items according to changes in the underlying
107        /// model.
108        fn watch_items(&self, pos: u32, removed: u32, added: u32) {
109            let Some(model) = self.model.obj() else {
110                return;
111            };
112
113            let expressions = self.expressions.borrow().clone();
114            if expressions.is_empty() {
115                return;
116            }
117
118            let mut new_entries = Vec::with_capacity(added as usize);
119            for item_pos in pos..pos + added {
120                let Some(item) = model.item(item_pos) else {
121                    error!("Out of bounds item");
122                    break;
123                };
124
125                let obj = self.obj();
126                let mut item_watches = Vec::with_capacity(expressions.len());
127                for expression in &expressions {
128                    item_watches.push(expression.watch(
129                        Some(&item),
130                        clone!(
131                            #[strong]
132                            obj,
133                            #[weak]
134                            item,
135                            move || {
136                                obj.imp().item_expr_changed(&item);
137                            }
138                        ),
139                    ));
140                }
141
142                new_entries.push((item, item_watches));
143            }
144
145            let mut watches = self.watches.borrow_mut();
146            let removed_range = (pos as usize)..((pos + removed) as usize);
147            for watch in watches
148                .splice(removed_range, new_entries)
149                .flat_map(|(_, w)| w)
150            {
151                watch.unwatch();
152            }
153        }
154
155        fn item_expr_changed(&self, item: &glib::Object) {
156            // O(1) lookup via the IndexMap.
157            let pos = self.watches.borrow().get_index_of(item);
158            if let Some(pos) = pos {
159                self.obj().items_changed(pos as u32, 1, 1);
160            }
161        }
162    }
163}
164
165glib::wrapper! {
166    /// A list model that signals an item as changed when the expression's value changes.
167    pub struct ExpressionListModel(ObjectSubclass<imp::ExpressionListModel>)
168        @implements gio::ListModel;
169}
170
171impl ExpressionListModel {
172    pub fn new() -> Self {
173        glib::Object::new()
174    }
175
176    /// Set the expressions to watch.
177    pub(crate) fn set_expressions(&self, expressions: Vec<gtk::Expression>) {
178        self.imp().set_expressions(expressions);
179    }
180}
181
182impl Default for ExpressionListModel {
183    fn default() -> Self {
184        Self::new()
185    }
186}