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