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