1use std::{fmt, ptr};
4
5use crate::{
6 ffi, gobject_ffi, object::ObjectRef, prelude::*, translate::*, Binding, BindingFlags,
7 BindingGroup, BoolError, Object, ParamSpec, Value,
8};
9
10impl BindingGroup {
11 #[doc(alias = "bind_with_closures")]
12 pub fn bind<'a, O: ObjectType>(
13 &'a self,
14 source_property: &'a str,
15 target: &'a O,
16 target_property: &'a str,
17 ) -> BindingGroupBuilder<'a> {
18 BindingGroupBuilder::new(self, source_property, target, target_property)
19 }
20}
21
22type TransformFn = Option<Box<dyn Fn(&Binding, &Value) -> Option<Value> + Send + Sync + 'static>>;
23
24#[must_use = "The builder must be built to be used"]
27pub struct BindingGroupBuilder<'a> {
28 group: &'a BindingGroup,
29 source_property: &'a str,
30 target: &'a ObjectRef,
31 target_property: &'a str,
32 flags: BindingFlags,
33 transform_to: TransformFn,
34 transform_from: TransformFn,
35}
36
37impl fmt::Debug for BindingGroupBuilder<'_> {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 f.debug_struct("BindingGroupBuilder")
40 .field("group", &self.group)
41 .field("source_property", &self.source_property)
42 .field("target", &self.target)
43 .field("target_property", &self.target_property)
44 .field("flags", &self.flags)
45 .finish()
46 }
47}
48
49impl<'a> BindingGroupBuilder<'a> {
50 fn new(
51 group: &'a BindingGroup,
52 source_property: &'a str,
53 target: &'a impl ObjectType,
54 target_property: &'a str,
55 ) -> Self {
56 Self {
57 group,
58 source_property,
59 target: target.as_object_ref(),
60 target_property,
61 flags: BindingFlags::DEFAULT,
62 transform_to: None,
63 transform_from: None,
64 }
65 }
66
67 pub fn transform_from<F: Fn(&Binding, &Value) -> Option<Value> + Send + Sync + 'static>(
70 self,
71 func: F,
72 ) -> Self {
73 Self {
74 transform_from: Some(Box::new(func)),
75 ..self
76 }
77 }
78
79 pub fn transform_to<F: Fn(&Binding, &Value) -> Option<Value> + Send + Sync + 'static>(
82 self,
83 func: F,
84 ) -> Self {
85 Self {
86 transform_to: Some(Box::new(func)),
87 ..self
88 }
89 }
90
91 pub fn flags(self, flags: BindingFlags) -> Self {
94 Self { flags, ..self }
95 }
96
97 pub fn bidirectional(mut self) -> Self {
100 self.flags |= crate::BindingFlags::BIDIRECTIONAL;
101 self
102 }
103
104 pub fn sync_create(mut self) -> Self {
107 self.flags |= crate::BindingFlags::SYNC_CREATE;
108 self
109 }
110
111 pub fn invert_boolean(mut self) -> Self {
114 self.flags |= crate::BindingFlags::INVERT_BOOLEAN;
115 self
116 }
117
118 pub fn try_build(self) -> Result<(), BoolError> {
123 unsafe extern "C" fn transform_to_trampoline(
124 binding: *mut gobject_ffi::GBinding,
125 from_value: *const gobject_ffi::GValue,
126 to_value: *mut gobject_ffi::GValue,
127 user_data: ffi::gpointer,
128 ) -> ffi::gboolean {
129 let transform_data =
130 &*(user_data as *const (TransformFn, TransformFn, String, ParamSpec));
131
132 match (transform_data.0.as_ref().unwrap())(
133 &from_glib_borrow(binding),
134 &*(from_value as *const Value),
135 ) {
136 None => false,
137 Some(res) => {
138 assert!(
139 res.type_().is_a(transform_data.3.value_type()),
140 "Target property {} expected type {} but transform_to function returned {}",
141 transform_data.3.name(),
142 transform_data.3.value_type(),
143 res.type_()
144 );
145 *to_value = res.into_raw();
146 true
147 }
148 }
149 .into_glib()
150 }
151
152 unsafe extern "C" fn transform_from_trampoline(
153 binding: *mut gobject_ffi::GBinding,
154 from_value: *const gobject_ffi::GValue,
155 to_value: *mut gobject_ffi::GValue,
156 user_data: ffi::gpointer,
157 ) -> ffi::gboolean {
158 let transform_data =
159 &*(user_data as *const (TransformFn, TransformFn, String, ParamSpec));
160 let binding = from_glib_borrow(binding);
161
162 match (transform_data.1.as_ref().unwrap())(
163 &binding,
164 &*(from_value as *const Value),
165 ) {
166 None => false,
167 Some(res) => {
168 let pspec_name = transform_data.2.clone();
169 let source = binding.source().unwrap();
170 let pspec = source.find_property(&pspec_name);
171 assert!(pspec.is_some(), "Source object does not have a property {pspec_name}");
172 let pspec = pspec.unwrap();
173
174 assert!(
175 res.type_().is_a(pspec.value_type()),
176 "Source property {pspec_name} expected type {} but transform_from function returned {}",
177 pspec.value_type(),
178 res.type_()
179 );
180 *to_value = res.into_raw();
181 true
182 }
183 }
184 .into_glib()
185 }
186
187 unsafe extern "C" fn free_transform_data(data: ffi::gpointer) {
188 let _ = Box::from_raw(data as *mut (TransformFn, TransformFn, String, ParamSpec));
189 }
190
191 let mut _source_property_name_cstr = None;
192 let source_property_name = if let Some(source) = self.group.source() {
193 let source_property = source.find_property(self.source_property).ok_or_else(|| {
194 bool_error!(
195 "Source property {} on type {} not found",
196 self.source_property,
197 source.type_()
198 )
199 })?;
200
201 source_property.name().as_ptr()
203 } else {
204 let source_property_name = std::ffi::CString::new(self.source_property).unwrap();
206 let source_property_name_ptr = source_property_name.as_ptr() as *const u8;
207 _source_property_name_cstr = Some(source_property_name);
208
209 source_property_name_ptr
210 };
211
212 unsafe {
213 let target: Object = from_glib_none(self.target.clone().to_glib_none().0);
214
215 let target_property = target.find_property(self.target_property).ok_or_else(|| {
216 bool_error!(
217 "Target property {} on type {} not found",
218 self.target_property,
219 target.type_()
220 )
221 })?;
222
223 let target_property_name = target_property.name().as_ptr();
224
225 let have_transform_to = self.transform_to.is_some();
226 let have_transform_from = self.transform_from.is_some();
227 let transform_data = if have_transform_to || have_transform_from {
228 Box::into_raw(Box::new((
229 self.transform_to,
230 self.transform_from,
231 String::from_glib_none(source_property_name as *const _),
232 target_property,
233 )))
234 } else {
235 ptr::null_mut()
236 };
237
238 gobject_ffi::g_binding_group_bind_full(
239 self.group.to_glib_none().0,
240 source_property_name as *const _,
241 target.to_glib_none().0,
242 target_property_name as *const _,
243 self.flags.into_glib(),
244 if have_transform_to {
245 Some(transform_to_trampoline)
246 } else {
247 None
248 },
249 if have_transform_from {
250 Some(transform_from_trampoline)
251 } else {
252 None
253 },
254 transform_data as ffi::gpointer,
255 if transform_data.is_null() {
256 None
257 } else {
258 Some(free_transform_data)
259 },
260 );
261 }
262
263 Ok(())
264 }
265
266 pub fn build(self) {
269 self.try_build().unwrap()
270 }
271}
272
273#[cfg(test)]
274mod test {
275 use crate::{prelude::*, subclass::prelude::*};
276
277 #[test]
278 fn binding_without_source() {
279 let binding_group = crate::BindingGroup::new();
280
281 let source = TestObject::default();
282 let target = TestObject::default();
283
284 assert!(source.find_property("name").is_some());
285 binding_group
286 .bind("name", &target, "name")
287 .bidirectional()
288 .build();
289
290 binding_group.set_source(Some(&source));
291
292 source.set_name("test_source_name");
293 assert_eq!(source.name(), target.name());
294
295 target.set_name("test_target_name");
296 assert_eq!(source.name(), target.name());
297 }
298
299 #[test]
300 fn binding_with_source() {
301 let binding_group = crate::BindingGroup::new();
302
303 let source = TestObject::default();
304 let target = TestObject::default();
305
306 binding_group.set_source(Some(&source));
307
308 binding_group.bind("name", &target, "name").build();
309
310 source.set_name("test_source_name");
311 assert_eq!(source.name(), target.name());
312 }
313
314 #[test]
315 fn binding_to_transform() {
316 let binding_group = crate::BindingGroup::new();
317
318 let source = TestObject::default();
319 let target = TestObject::default();
320
321 binding_group.set_source(Some(&source));
322 binding_group
323 .bind("name", &target, "name")
324 .sync_create()
325 .transform_to(|_binding, value| {
326 let value = value.get::<&str>().unwrap();
327 Some(format!("{value} World").to_value())
328 })
329 .transform_from(|_binding, value| {
330 let value = value.get::<&str>().unwrap();
331 Some(format!("{value} World").to_value())
332 })
333 .build();
334
335 source.set_name("Hello");
336 assert_eq!(target.name(), "Hello World");
337 }
338
339 #[test]
340 fn binding_from_transform() {
341 let binding_group = crate::BindingGroup::new();
342
343 let source = TestObject::default();
344 let target = TestObject::default();
345
346 binding_group.set_source(Some(&source));
347 binding_group
348 .bind("name", &target, "name")
349 .sync_create()
350 .bidirectional()
351 .transform_to(|_binding, value| {
352 let value = value.get::<&str>().unwrap();
353 Some(format!("{value} World").to_value())
354 })
355 .transform_from(|_binding, value| {
356 let value = value.get::<&str>().unwrap();
357 Some(format!("{value} World").to_value())
358 })
359 .build();
360
361 target.set_name("Hello");
362 assert_eq!(source.name(), "Hello World");
363 }
364
365 #[test]
366 fn binding_to_transform_change_type() {
367 let binding_group = crate::BindingGroup::new();
368
369 let source = TestObject::default();
370 let target = TestObject::default();
371
372 binding_group.set_source(Some(&source));
373 binding_group
374 .bind("name", &target, "enabled")
375 .sync_create()
376 .transform_to(|_binding, value| {
377 let value = value.get::<&str>().unwrap();
378 Some((value == "Hello").to_value())
379 })
380 .transform_from(|_binding, value| {
381 let value = value.get::<bool>().unwrap();
382 Some((if value { "Hello" } else { "World" }).to_value())
383 })
384 .build();
385
386 source.set_name("Hello");
387 assert!(target.enabled());
388
389 source.set_name("Hello World");
390 assert!(!target.enabled());
391 }
392
393 #[test]
394 fn binding_from_transform_change_type() {
395 let binding_group = crate::BindingGroup::new();
396
397 let source = TestObject::default();
398 let target = TestObject::default();
399
400 binding_group.set_source(Some(&source));
401 binding_group
402 .bind("name", &target, "enabled")
403 .sync_create()
404 .bidirectional()
405 .transform_to(|_binding, value| {
406 let value = value.get::<&str>().unwrap();
407 Some((value == "Hello").to_value())
408 })
409 .transform_from(|_binding, value| {
410 let value = value.get::<bool>().unwrap();
411 Some((if value { "Hello" } else { "World" }).to_value())
412 })
413 .build();
414
415 target.set_enabled(true);
416 assert_eq!(source.name(), "Hello");
417 target.set_enabled(false);
418 assert_eq!(source.name(), "World");
419 }
420
421 mod imp {
422 use std::{cell::RefCell, sync::OnceLock};
423
424 use super::*;
425 use crate as glib;
426
427 #[derive(Debug, Default)]
428 pub struct TestObject {
429 pub name: RefCell<String>,
430 pub enabled: RefCell<bool>,
431 }
432
433 #[crate::object_subclass]
434 impl ObjectSubclass for TestObject {
435 const NAME: &'static str = "TestBindingGroup";
436 type Type = super::TestObject;
437 }
438
439 impl ObjectImpl for TestObject {
440 fn properties() -> &'static [crate::ParamSpec] {
441 static PROPERTIES: OnceLock<Vec<crate::ParamSpec>> = OnceLock::new();
442 PROPERTIES.get_or_init(|| {
443 vec![
444 crate::ParamSpecString::builder("name")
445 .explicit_notify()
446 .build(),
447 crate::ParamSpecBoolean::builder("enabled")
448 .explicit_notify()
449 .build(),
450 ]
451 })
452 }
453
454 fn property(&self, _id: usize, pspec: &crate::ParamSpec) -> crate::Value {
455 let obj = self.obj();
456 match pspec.name() {
457 "name" => obj.name().to_value(),
458 "enabled" => obj.enabled().to_value(),
459 _ => unimplemented!(),
460 }
461 }
462
463 fn set_property(&self, _id: usize, value: &crate::Value, pspec: &crate::ParamSpec) {
464 let obj = self.obj();
465 match pspec.name() {
466 "name" => obj.set_name(value.get().unwrap()),
467 "enabled" => obj.set_enabled(value.get().unwrap()),
468 _ => unimplemented!(),
469 };
470 }
471 }
472 }
473
474 crate::wrapper! {
475 pub struct TestObject(ObjectSubclass<imp::TestObject>);
476 }
477
478 impl Default for TestObject {
479 fn default() -> Self {
480 crate::Object::new()
481 }
482 }
483
484 impl TestObject {
485 fn name(&self) -> String {
486 self.imp().name.borrow().clone()
487 }
488
489 fn set_name(&self, name: &str) {
490 if name != self.imp().name.replace(name.to_string()).as_str() {
491 self.notify("name");
492 }
493 }
494
495 fn enabled(&self) -> bool {
496 *self.imp().enabled.borrow()
497 }
498
499 fn set_enabled(&self, enabled: bool) {
500 if enabled != self.imp().enabled.replace(enabled) {
501 self.notify("enabled");
502 }
503 }
504 }
505}