fractal/utils/
expression_list_model.rs1use 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 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 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 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 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 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}