glib/gobject/
binding.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::{prelude::*, Binding, Object};
4
5impl Binding {
6    #[doc(alias = "get_source")]
7    pub fn source(&self) -> Option<Object> {
8        self.property("source")
9    }
10
11    #[doc(alias = "get_target")]
12    pub fn target(&self) -> Option<Object> {
13        self.property("target")
14    }
15}
16
17#[cfg(test)]
18mod test {
19    use crate::{prelude::*, subclass::prelude::*};
20
21    #[test]
22    fn binding() {
23        let source = TestObject::default();
24        let target = TestObject::default();
25
26        assert!(source.find_property("name").is_some());
27        source
28            .bind_property("name", &target, "name")
29            .bidirectional()
30            .build();
31
32        source.set_name("test_source_name");
33        assert_eq!(source.name(), target.name());
34
35        target.set_name("test_target_name");
36        assert_eq!(source.name(), target.name());
37    }
38
39    #[test]
40    fn binding_to_transform_with_values() {
41        let source = TestObject::default();
42        let target = TestObject::default();
43
44        source
45            .bind_property("name", &target, "name")
46            .sync_create()
47            .transform_to_with_values(|_binding, value| {
48                let value = value.get::<&str>().unwrap();
49                Some(format!("{value} World").to_value())
50            })
51            .transform_from_with_values(|_binding, value| {
52                let value = value.get::<&str>().unwrap();
53                Some(format!("{value} World").to_value())
54            })
55            .build();
56
57        source.set_name("Hello");
58        assert_eq!(target.name(), "Hello World");
59    }
60
61    #[test]
62    fn binding_from_transform_with_values() {
63        let source = TestObject::default();
64        let target = TestObject::default();
65
66        source
67            .bind_property("name", &target, "name")
68            .sync_create()
69            .bidirectional()
70            .transform_to_with_values(|_binding, value| {
71                let value = value.get::<&str>().unwrap();
72                Some(format!("{value} World").to_value())
73            })
74            .transform_from_with_values(|_binding, value| {
75                let value = value.get::<&str>().unwrap();
76                Some(format!("{value} World").to_value())
77            })
78            .build();
79
80        target.set_name("Hello");
81        assert_eq!(source.name(), "Hello World");
82    }
83
84    #[test]
85    fn binding_to_transform_ref() {
86        let source = TestObject::default();
87        let target = TestObject::default();
88
89        source
90            .bind_property("name", &target, "name")
91            .sync_create()
92            .transform_to(|_binding, value: &str| Some(format!("{value} World")))
93            .transform_from(|_binding, value: &str| Some(format!("{value} World")))
94            .build();
95
96        source.set_name("Hello");
97        assert_eq!(target.name(), "Hello World");
98    }
99
100    #[test]
101    fn binding_to_transform_owned_ref() {
102        let source = TestObject::default();
103        let target = TestObject::default();
104
105        source
106            .bind_property("name", &target, "name")
107            .sync_create()
108            .transform_to(|_binding, value: String| Some(format!("{value} World")))
109            .transform_from(|_binding, value: &str| Some(format!("{value} World")))
110            .build();
111
112        source.set_name("Hello");
113        assert_eq!(target.name(), "Hello World");
114    }
115
116    #[test]
117    fn binding_from_transform() {
118        let source = TestObject::default();
119        let target = TestObject::default();
120
121        source
122            .bind_property("name", &target, "name")
123            .sync_create()
124            .bidirectional()
125            .transform_to(|_binding, value: &str| Some(format!("{value} World")))
126            .transform_from(|_binding, value: &str| Some(format!("{value} World")))
127            .build();
128
129        target.set_name("Hello");
130        assert_eq!(source.name(), "Hello World");
131    }
132
133    #[test]
134    fn binding_to_transform_with_values_change_type() {
135        let source = TestObject::default();
136        let target = TestObject::default();
137
138        source
139            .bind_property("name", &target, "enabled")
140            .sync_create()
141            .transform_to_with_values(|_binding, value| {
142                let value = value.get::<&str>().unwrap();
143                Some((value == "Hello").to_value())
144            })
145            .transform_from_with_values(|_binding, value| {
146                let value = value.get::<bool>().unwrap();
147                Some((if value { "Hello" } else { "World" }).to_value())
148            })
149            .build();
150
151        source.set_name("Hello");
152        assert!(target.enabled());
153
154        source.set_name("Hello World");
155        assert!(!target.enabled());
156    }
157
158    #[test]
159    fn binding_from_transform_values_change_type() {
160        let source = TestObject::default();
161        let target = TestObject::default();
162
163        source
164            .bind_property("name", &target, "enabled")
165            .sync_create()
166            .bidirectional()
167            .transform_to_with_values(|_binding, value| {
168                let value = value.get::<&str>().unwrap();
169                Some((value == "Hello").to_value())
170            })
171            .transform_from_with_values(|_binding, value| {
172                let value = value.get::<bool>().unwrap();
173                Some((if value { "Hello" } else { "World" }).to_value())
174            })
175            .build();
176
177        target.set_enabled(true);
178        assert_eq!(source.name(), "Hello");
179        target.set_enabled(false);
180        assert_eq!(source.name(), "World");
181    }
182
183    #[test]
184    fn binding_to_transform_change_type() {
185        let source = TestObject::default();
186        let target = TestObject::default();
187
188        source
189            .bind_property("name", &target, "enabled")
190            .sync_create()
191            .transform_to(|_binding, value: &str| Some(value == "Hello"))
192            .transform_from(|_binding, value: bool| Some(if value { "Hello" } else { "World" }))
193            .build();
194
195        source.set_name("Hello");
196        assert!(target.enabled());
197
198        source.set_name("Hello World");
199        assert!(!target.enabled());
200    }
201
202    #[test]
203    fn binding_from_transform_change_type() {
204        let source = TestObject::default();
205        let target = TestObject::default();
206
207        source
208            .bind_property("name", &target, "enabled")
209            .sync_create()
210            .bidirectional()
211            .transform_to(|_binding, value: &str| Some(value == "Hello"))
212            .transform_from(|_binding, value: bool| Some(if value { "Hello" } else { "World" }))
213            .build();
214
215        target.set_enabled(true);
216        assert_eq!(source.name(), "Hello");
217        target.set_enabled(false);
218        assert_eq!(source.name(), "World");
219    }
220
221    mod imp {
222        use std::{cell::RefCell, sync::OnceLock};
223
224        use super::*;
225        use crate as glib;
226
227        #[derive(Debug, Default)]
228        pub struct TestObject {
229            pub name: RefCell<String>,
230            pub enabled: RefCell<bool>,
231        }
232
233        #[crate::object_subclass]
234        impl ObjectSubclass for TestObject {
235            const NAME: &'static str = "TestBinding";
236            type Type = super::TestObject;
237        }
238
239        impl ObjectImpl for TestObject {
240            fn properties() -> &'static [crate::ParamSpec] {
241                static PROPERTIES: OnceLock<Vec<crate::ParamSpec>> = OnceLock::new();
242                PROPERTIES.get_or_init(|| {
243                    vec![
244                        crate::ParamSpecString::builder("name")
245                            .explicit_notify()
246                            .build(),
247                        crate::ParamSpecBoolean::builder("enabled")
248                            .explicit_notify()
249                            .build(),
250                    ]
251                })
252            }
253
254            fn property(&self, _id: usize, pspec: &crate::ParamSpec) -> crate::Value {
255                let obj = self.obj();
256                match pspec.name() {
257                    "name" => obj.name().to_value(),
258                    "enabled" => obj.enabled().to_value(),
259                    _ => unimplemented!(),
260                }
261            }
262
263            fn set_property(&self, _id: usize, value: &crate::Value, pspec: &crate::ParamSpec) {
264                let obj = self.obj();
265                match pspec.name() {
266                    "name" => obj.set_name(value.get().unwrap()),
267                    "enabled" => obj.set_enabled(value.get().unwrap()),
268                    _ => unimplemented!(),
269                };
270            }
271        }
272    }
273
274    crate::wrapper! {
275        pub struct TestObject(ObjectSubclass<imp::TestObject>);
276    }
277
278    impl Default for TestObject {
279        fn default() -> Self {
280            crate::Object::new()
281        }
282    }
283
284    impl TestObject {
285        fn name(&self) -> String {
286            self.imp().name.borrow().clone()
287        }
288
289        fn set_name(&self, name: &str) {
290            if name != self.imp().name.replace(name.to_string()).as_str() {
291                self.notify("name");
292            }
293        }
294
295        fn enabled(&self) -> bool {
296            *self.imp().enabled.borrow()
297        }
298
299        fn set_enabled(&self, enabled: bool) {
300            if enabled != self.imp().enabled.replace(enabled) {
301                self.notify("enabled");
302            }
303        }
304    }
305}