glib/gobject/
binding.rs
1use 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}