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