1use std::{fmt, ptr};
4
5use crate::{
6 Binding, BindingFlags, BindingGroup, BoolError, Object, ParamSpec, Value, ffi, gobject_ffi,
7 object::ObjectRef, prelude::*, translate::*, value::FromValue,
8};
9
10impl BindingGroup {
11 #[doc(alias = "bind_with_closures")]
26 pub fn bind<'a, 'f, 't, O: ObjectType>(
27 &'a self,
28 source_property: &'a str,
29 target: &'a O,
30 target_property: &'a str,
31 ) -> BindingGroupBuilder<'a, 'f, 't> {
32 BindingGroupBuilder::new(self, source_property, target, target_property)
33 }
34}
35
36type TransformFn<'b> =
37 Option<Box<dyn Fn(&'b Binding, &'b Value) -> Option<Value> + Send + Sync + 'static>>;
38
39#[must_use = "The builder must be built to be used"]
42pub struct BindingGroupBuilder<'a, 'f, 't> {
43 group: &'a BindingGroup,
44 source_property: &'a str,
45 target: &'a ObjectRef,
46 target_property: &'a str,
47 flags: BindingFlags,
48 transform_to: TransformFn<'t>,
49 transform_from: TransformFn<'f>,
50}
51
52impl fmt::Debug for BindingGroupBuilder<'_, '_, '_> {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 f.debug_struct("BindingGroupBuilder")
55 .field("group", &self.group)
56 .field("source_property", &self.source_property)
57 .field("target", &self.target)
58 .field("target_property", &self.target_property)
59 .field("flags", &self.flags)
60 .finish()
61 }
62}
63
64impl<'a, 'f, 't> BindingGroupBuilder<'a, 'f, 't> {
65 fn new(
66 group: &'a BindingGroup,
67 source_property: &'a str,
68 target: &'a impl ObjectType,
69 target_property: &'a str,
70 ) -> Self {
71 Self {
72 group,
73 source_property,
74 target: target.as_object_ref(),
75 target_property,
76 flags: BindingFlags::DEFAULT,
77 transform_to: None,
78 transform_from: None,
79 }
80 }
81
82 pub fn transform_from_with_values<
85 F: Fn(&Binding, &Value) -> Option<Value> + Send + Sync + 'static,
86 >(
87 self,
88 func: F,
89 ) -> Self {
90 Self {
91 transform_from: Some(Box::new(func)),
92 ..self
93 }
94 }
95
96 pub fn transform_from<
102 S: FromValue<'f>,
103 T: Into<Value>,
104 F: Fn(&'f Binding, S) -> Option<T> + Send + Sync + 'static,
105 >(
106 self,
107 func: F,
108 ) -> Self {
109 Self {
110 transform_from: Some(Box::new(move |binding, from_value| {
111 let from_value = from_value.get().expect("Wrong value type");
112 func(binding, from_value).map(|r| r.into())
113 })),
114 ..self
115 }
116 }
117
118 pub fn transform_to_with_values<
121 F: Fn(&Binding, &Value) -> Option<Value> + Send + Sync + 'static,
122 >(
123 self,
124 func: F,
125 ) -> Self {
126 Self {
127 transform_to: Some(Box::new(func)),
128 ..self
129 }
130 }
131
132 pub fn transform_to<
138 S: FromValue<'t>,
139 T: Into<Value>,
140 F: Fn(&'t Binding, S) -> Option<T> + Send + Sync + 'static,
141 >(
142 self,
143 func: F,
144 ) -> Self {
145 Self {
146 transform_to: Some(Box::new(move |binding, from_value| {
147 let from_value = from_value.get().expect("Wrong value type");
148 func(binding, from_value).map(|r| r.into())
149 })),
150 ..self
151 }
152 }
153
154 pub fn flags(self, flags: BindingFlags) -> Self {
157 Self { flags, ..self }
158 }
159
160 pub fn bidirectional(mut self) -> Self {
163 self.flags |= crate::BindingFlags::BIDIRECTIONAL;
164 self
165 }
166
167 pub fn sync_create(mut self) -> Self {
170 self.flags |= crate::BindingFlags::SYNC_CREATE;
171 self
172 }
173
174 pub fn invert_boolean(mut self) -> Self {
177 self.flags |= crate::BindingFlags::INVERT_BOOLEAN;
178 self
179 }
180
181 pub fn try_build(self) -> Result<(), BoolError> {
186 unsafe extern "C" fn transform_to_trampoline(
187 binding: *mut gobject_ffi::GBinding,
188 from_value: *const gobject_ffi::GValue,
189 to_value: *mut gobject_ffi::GValue,
190 user_data: ffi::gpointer,
191 ) -> ffi::gboolean {
192 unsafe {
193 let transform_data =
194 &*(user_data as *const (TransformFn, TransformFn, String, ParamSpec));
195
196 match (transform_data.0.as_ref().unwrap())(
197 &from_glib_borrow(binding),
198 &*(from_value as *const Value),
199 ) {
200 None => false,
201 Some(res) => {
202 assert!(
203 res.type_().is_a(transform_data.3.value_type()),
204 "Target property {} expected type {} but transform_to function returned {}",
205 transform_data.3.name(),
206 transform_data.3.value_type(),
207 res.type_()
208 );
209 *to_value = res.into_raw();
210 true
211 }
212 }
213 .into_glib()
214 }
215 }
216
217 unsafe extern "C" fn transform_from_trampoline(
218 binding: *mut gobject_ffi::GBinding,
219 from_value: *const gobject_ffi::GValue,
220 to_value: *mut gobject_ffi::GValue,
221 user_data: ffi::gpointer,
222 ) -> ffi::gboolean {
223 unsafe {
224 let transform_data =
225 &*(user_data as *const (TransformFn, TransformFn, String, ParamSpec));
226 let binding = from_glib_borrow(binding);
227
228 match (transform_data.1.as_ref().unwrap())(
229 &binding,
230 &*(from_value as *const Value),
231 ) {
232 None => false,
233 Some(res) => {
234 let pspec_name = transform_data.2.clone();
235 let source = binding.source().unwrap();
236 let pspec = source.find_property(&pspec_name);
237 assert!(pspec.is_some(), "Source object does not have a property {pspec_name}");
238 let pspec = pspec.unwrap();
239
240 assert!(
241 res.type_().is_a(pspec.value_type()),
242 "Source property {pspec_name} expected type {} but transform_from function returned {}",
243 pspec.value_type(),
244 res.type_()
245 );
246 *to_value = res.into_raw();
247 true
248 }
249 }
250 .into_glib()
251 }
252 }
253
254 unsafe extern "C" fn free_transform_data(data: ffi::gpointer) {
255 unsafe {
256 let _ = Box::from_raw(data as *mut (TransformFn, TransformFn, String, ParamSpec));
257 }
258 }
259
260 let mut _source_property_name_cstr = None;
261 let source_property_name = match self.group.source() {
262 Some(source) => {
263 let source_property =
264 source.find_property(self.source_property).ok_or_else(|| {
265 bool_error!(
266 "Source property {} on type {} not found",
267 self.source_property,
268 source.type_()
269 )
270 })?;
271
272 source_property.name().as_ptr()
274 }
275 _ => {
276 let source_property_name = std::ffi::CString::new(self.source_property).unwrap();
278 let source_property_name_ptr = source_property_name.as_ptr() as *const u8;
279 _source_property_name_cstr = Some(source_property_name);
280
281 source_property_name_ptr
282 }
283 };
284
285 unsafe {
286 let target: Object = from_glib_none(self.target.clone().to_glib_none().0);
287
288 let target_property = target.find_property(self.target_property).ok_or_else(|| {
289 bool_error!(
290 "Target property {} on type {} not found",
291 self.target_property,
292 target.type_()
293 )
294 })?;
295
296 let target_property_name = target_property.name().as_ptr();
297
298 let have_transform_to = self.transform_to.is_some();
299 let have_transform_from = self.transform_from.is_some();
300 let transform_data = if have_transform_to || have_transform_from {
301 Box::into_raw(Box::new((
302 self.transform_to,
303 self.transform_from,
304 String::from_glib_none(source_property_name as *const _),
305 target_property,
306 )))
307 } else {
308 ptr::null_mut()
309 };
310
311 gobject_ffi::g_binding_group_bind_full(
312 self.group.to_glib_none().0,
313 source_property_name as *const _,
314 target.to_glib_none().0,
315 target_property_name as *const _,
316 self.flags.into_glib(),
317 if have_transform_to {
318 Some(transform_to_trampoline)
319 } else {
320 None
321 },
322 if have_transform_from {
323 Some(transform_from_trampoline)
324 } else {
325 None
326 },
327 transform_data as ffi::gpointer,
328 if transform_data.is_null() {
329 None
330 } else {
331 Some(free_transform_data)
332 },
333 );
334 }
335
336 Ok(())
337 }
338
339 pub fn build(self) {
342 self.try_build().unwrap()
343 }
344}
345
346#[cfg(test)]
347mod test {
348 use crate::{prelude::*, subclass::prelude::*};
349
350 #[test]
351 fn binding_without_source() {
352 let binding_group = crate::BindingGroup::new();
353
354 let source = TestObject::default();
355 let target = TestObject::default();
356
357 assert!(source.find_property("name").is_some());
358 binding_group
359 .bind("name", &target, "name")
360 .bidirectional()
361 .build();
362
363 binding_group.set_source(Some(&source));
364
365 source.set_name("test_source_name");
366 assert_eq!(source.name(), target.name());
367
368 target.set_name("test_target_name");
369 assert_eq!(source.name(), target.name());
370 }
371
372 #[test]
373 fn binding_with_source() {
374 let binding_group = crate::BindingGroup::new();
375
376 let source = TestObject::default();
377 let target = TestObject::default();
378
379 binding_group.set_source(Some(&source));
380
381 binding_group.bind("name", &target, "name").build();
382
383 source.set_name("test_source_name");
384 assert_eq!(source.name(), target.name());
385 }
386
387 #[test]
388 fn binding_to_transform() {
389 let binding_group = crate::BindingGroup::new();
390
391 let source = TestObject::default();
392 let target = TestObject::default();
393
394 binding_group.set_source(Some(&source));
395 binding_group
396 .bind("name", &target, "name")
397 .sync_create()
398 .transform_to_with_values(|_binding, value| {
399 let value = value.get::<&str>().unwrap();
400 Some(format!("{value} World").to_value())
401 })
402 .transform_from_with_values(|_binding, value| {
403 let value = value.get::<&str>().unwrap();
404 Some(format!("{value} World").to_value())
405 })
406 .build();
407
408 source.set_name("Hello");
409 assert_eq!(target.name(), "Hello World");
410 }
411
412 #[test]
413 fn binding_from_transform() {
414 let binding_group = crate::BindingGroup::new();
415
416 let source = TestObject::default();
417 let target = TestObject::default();
418
419 binding_group.set_source(Some(&source));
420 binding_group
421 .bind("name", &target, "name")
422 .sync_create()
423 .bidirectional()
424 .transform_to_with_values(|_binding, value| {
425 let value = value.get::<&str>().unwrap();
426 Some(format!("{value} World").to_value())
427 })
428 .transform_from_with_values(|_binding, value| {
429 let value = value.get::<&str>().unwrap();
430 Some(format!("{value} World").to_value())
431 })
432 .build();
433
434 target.set_name("Hello");
435 assert_eq!(source.name(), "Hello World");
436 }
437
438 #[test]
439 fn binding_to_transform_change_type() {
440 let binding_group = crate::BindingGroup::new();
441
442 let source = TestObject::default();
443 let target = TestObject::default();
444
445 binding_group.set_source(Some(&source));
446 binding_group
447 .bind("name", &target, "enabled")
448 .sync_create()
449 .transform_to_with_values(|_binding, value| {
450 let value = value.get::<&str>().unwrap();
451 Some((value == "Hello").to_value())
452 })
453 .transform_from_with_values(|_binding, value| {
454 let value = value.get::<bool>().unwrap();
455 Some((if value { "Hello" } else { "World" }).to_value())
456 })
457 .build();
458
459 source.set_name("Hello");
460 assert!(target.enabled());
461
462 source.set_name("Hello World");
463 assert!(!target.enabled());
464 }
465
466 #[test]
467 fn binding_from_transform_change_type() {
468 let binding_group = crate::BindingGroup::new();
469
470 let source = TestObject::default();
471 let target = TestObject::default();
472
473 binding_group.set_source(Some(&source));
474 binding_group
475 .bind("name", &target, "enabled")
476 .sync_create()
477 .bidirectional()
478 .transform_to_with_values(|_binding, value| {
479 let value = value.get::<&str>().unwrap();
480 Some((value == "Hello").to_value())
481 })
482 .transform_from_with_values(|_binding, value| {
483 let value = value.get::<bool>().unwrap();
484 Some((if value { "Hello" } else { "World" }).to_value())
485 })
486 .build();
487
488 target.set_enabled(true);
489 assert_eq!(source.name(), "Hello");
490 target.set_enabled(false);
491 assert_eq!(source.name(), "World");
492 }
493
494 #[test]
495 fn binding_from_transform_concrete_change_type() {
496 let binding_group = crate::BindingGroup::new();
497
498 let source = TestObject::default();
499 let target = TestObject::default();
500
501 binding_group.set_source(Some(&source));
502 binding_group
503 .bind("name", &target, "enabled")
504 .sync_create()
505 .bidirectional()
506 .transform_to::<&str, _, _>(|_binding, value| Some(value == "Hello"))
507 .transform_from(
508 |_binding, value: bool| if value { Some("Hello") } else { Some("World") },
509 )
510 .build();
511
512 target.set_enabled(true);
513 assert_eq!(source.name(), "Hello");
514 target.set_enabled(false);
515 assert_eq!(source.name(), "World");
516
517 source.set_name("Hello");
518 assert!(target.enabled());
519 source.set_name("World");
520 assert!(!target.enabled());
521 }
522
523 mod imp {
524 use std::{cell::RefCell, sync::OnceLock};
525
526 use super::*;
527 use crate as glib;
528
529 #[derive(Debug, Default)]
530 pub struct TestObject {
531 pub name: RefCell<String>,
532 pub enabled: RefCell<bool>,
533 }
534
535 #[crate::object_subclass]
536 impl ObjectSubclass for TestObject {
537 const NAME: &'static str = "TestBindingGroup";
538 type Type = super::TestObject;
539 }
540
541 impl ObjectImpl for TestObject {
542 fn properties() -> &'static [crate::ParamSpec] {
543 static PROPERTIES: OnceLock<Vec<crate::ParamSpec>> = OnceLock::new();
544 PROPERTIES.get_or_init(|| {
545 vec![
546 crate::ParamSpecString::builder("name")
547 .explicit_notify()
548 .build(),
549 crate::ParamSpecBoolean::builder("enabled")
550 .explicit_notify()
551 .build(),
552 ]
553 })
554 }
555
556 fn property(&self, _id: usize, pspec: &crate::ParamSpec) -> crate::Value {
557 let obj = self.obj();
558 match pspec.name() {
559 "name" => obj.name().to_value(),
560 "enabled" => obj.enabled().to_value(),
561 _ => unimplemented!(),
562 }
563 }
564
565 fn set_property(&self, _id: usize, value: &crate::Value, pspec: &crate::ParamSpec) {
566 let obj = self.obj();
567 match pspec.name() {
568 "name" => obj.set_name(value.get().unwrap()),
569 "enabled" => obj.set_enabled(value.get().unwrap()),
570 _ => unimplemented!(),
571 };
572 }
573 }
574 }
575
576 crate::wrapper! {
577 pub struct TestObject(ObjectSubclass<imp::TestObject>);
578 }
579
580 impl Default for TestObject {
581 fn default() -> Self {
582 crate::Object::new()
583 }
584 }
585
586 impl TestObject {
587 fn name(&self) -> String {
588 self.imp().name.borrow().clone()
589 }
590
591 fn set_name(&self, name: &str) {
592 if name != self.imp().name.replace(name.to_string()).as_str() {
593 self.notify("name");
594 }
595 }
596
597 fn enabled(&self) -> bool {
598 *self.imp().enabled.borrow()
599 }
600
601 fn set_enabled(&self, enabled: bool) {
602 if enabled != self.imp().enabled.replace(enabled) {
603 self.notify("enabled");
604 }
605 }
606 }
607}