fractal/utils/
expression_list_model.rs

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