1use std::{fmt, ptr};
4
5use crate::{
6 Binding, BindingFlags, BindingGroup, BoolError, Object, ParamSpec, Value, ffi, gobject_ffi,
7 object::ObjectRef, prelude::*, translate::*,
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 unsafe {
159 let transform_data =
160 &*(user_data as *const (TransformFn, TransformFn, String, ParamSpec));
161
162 match (transform_data.0.as_ref().unwrap())(
163 &from_glib_borrow(binding),
164 &*(from_value as *const Value),
165 ) {
166 None => false,
167 Some(res) => {
168 assert!(
169 res.type_().is_a(transform_data.3.value_type()),
170 "Target property {} expected type {} but transform_to function returned {}",
171 transform_data.3.name(),
172 transform_data.3.value_type(),
173 res.type_()
174 );
175 *to_value = res.into_raw();
176 true
177 }
178 }
179 .into_glib()
180 }
181 }
182
183 unsafe extern "C" fn transform_from_trampoline(
184 binding: *mut gobject_ffi::GBinding,
185 from_value: *const gobject_ffi::GValue,
186 to_value: *mut gobject_ffi::GValue,
187 user_data: ffi::gpointer,
188 ) -> ffi::gboolean {
189 unsafe {
190 let transform_data =
191 &*(user_data as *const (TransformFn, TransformFn, String, ParamSpec));
192 let binding = from_glib_borrow(binding);
193
194 match (transform_data.1.as_ref().unwrap())(
195 &binding,
196 &*(from_value as *const Value),
197 ) {
198 None => false,
199 Some(res) => {
200 let pspec_name = transform_data.2.clone();
201 let source = binding.source().unwrap();
202 let pspec = source.find_property(&pspec_name);
203 assert!(pspec.is_some(), "Source object does not have a property {pspec_name}");
204 let pspec = pspec.unwrap();
205
206 assert!(
207 res.type_().is_a(pspec.value_type()),
208 "Source property {pspec_name} expected type {} but transform_from function returned {}",
209 pspec.value_type(),
210 res.type_()
211 );
212 *to_value = res.into_raw();
213 true
214 }
215 }
216 .into_glib()
217 }
218 }
219
220 unsafe extern "C" fn free_transform_data(data: ffi::gpointer) {
221 unsafe {
222 let _ = Box::from_raw(data as *mut (TransformFn, TransformFn, String, ParamSpec));
223 }
224 }
225
226 let mut _source_property_name_cstr = None;
227 let source_property_name = match self.group.source() {
228 Some(source) => {
229 let source_property =
230 source.find_property(self.source_property).ok_or_else(|| {
231 bool_error!(
232 "Source property {} on type {} not found",
233 self.source_property,
234 source.type_()
235 )
236 })?;
237
238 source_property.name().as_ptr()
240 }
241 _ => {
242 let source_property_name = std::ffi::CString::new(self.source_property).unwrap();
244 let source_property_name_ptr = source_property_name.as_ptr() as *const u8;
245 _source_property_name_cstr = Some(source_property_name);
246
247 source_property_name_ptr
248 }
249 };
250
251 unsafe {
252 let target: Object = from_glib_none(self.target.clone().to_glib_none().0);
253
254 let target_property = target.find_property(self.target_property).ok_or_else(|| {
255 bool_error!(
256 "Target property {} on type {} not found",
257 self.target_property,
258 target.type_()
259 )
260 })?;
261
262 let target_property_name = target_property.name().as_ptr();
263
264 let have_transform_to = self.transform_to.is_some();
265 let have_transform_from = self.transform_from.is_some();
266 let transform_data = if have_transform_to || have_transform_from {
267 Box::into_raw(Box::new((
268 self.transform_to,
269 self.transform_from,
270 String::from_glib_none(source_property_name as *const _),
271 target_property,
272 )))
273 } else {
274 ptr::null_mut()
275 };
276
277 gobject_ffi::g_binding_group_bind_full(
278 self.group.to_glib_none().0,
279 source_property_name as *const _,
280 target.to_glib_none().0,
281 target_property_name as *const _,
282 self.flags.into_glib(),
283 if have_transform_to {
284 Some(transform_to_trampoline)
285 } else {
286 None
287 },
288 if have_transform_from {
289 Some(transform_from_trampoline)
290 } else {
291 None
292 },
293 transform_data as ffi::gpointer,
294 if transform_data.is_null() {
295 None
296 } else {
297 Some(free_transform_data)
298 },
299 );
300 }
301
302 Ok(())
303 }
304
305 pub fn build(self) {
308 self.try_build().unwrap()
309 }
310}
311
312#[cfg(test)]
313mod test {
314 use crate::{prelude::*, subclass::prelude::*};
315
316 #[test]
317 fn binding_without_source() {
318 let binding_group = crate::BindingGroup::new();
319
320 let source = TestObject::default();
321 let target = TestObject::default();
322
323 assert!(source.find_property("name").is_some());
324 binding_group
325 .bind("name", &target, "name")
326 .bidirectional()
327 .build();
328
329 binding_group.set_source(Some(&source));
330
331 source.set_name("test_source_name");
332 assert_eq!(source.name(), target.name());
333
334 target.set_name("test_target_name");
335 assert_eq!(source.name(), target.name());
336 }
337
338 #[test]
339 fn binding_with_source() {
340 let binding_group = crate::BindingGroup::new();
341
342 let source = TestObject::default();
343 let target = TestObject::default();
344
345 binding_group.set_source(Some(&source));
346
347 binding_group.bind("name", &target, "name").build();
348
349 source.set_name("test_source_name");
350 assert_eq!(source.name(), target.name());
351 }
352
353 #[test]
354 fn binding_to_transform() {
355 let binding_group = crate::BindingGroup::new();
356
357 let source = TestObject::default();
358 let target = TestObject::default();
359
360 binding_group.set_source(Some(&source));
361 binding_group
362 .bind("name", &target, "name")
363 .sync_create()
364 .transform_to(|_binding, value| {
365 let value = value.get::<&str>().unwrap();
366 Some(format!("{value} World").to_value())
367 })
368 .transform_from(|_binding, value| {
369 let value = value.get::<&str>().unwrap();
370 Some(format!("{value} World").to_value())
371 })
372 .build();
373
374 source.set_name("Hello");
375 assert_eq!(target.name(), "Hello World");
376 }
377
378 #[test]
379 fn binding_from_transform() {
380 let binding_group = crate::BindingGroup::new();
381
382 let source = TestObject::default();
383 let target = TestObject::default();
384
385 binding_group.set_source(Some(&source));
386 binding_group
387 .bind("name", &target, "name")
388 .sync_create()
389 .bidirectional()
390 .transform_to(|_binding, value| {
391 let value = value.get::<&str>().unwrap();
392 Some(format!("{value} World").to_value())
393 })
394 .transform_from(|_binding, value| {
395 let value = value.get::<&str>().unwrap();
396 Some(format!("{value} World").to_value())
397 })
398 .build();
399
400 target.set_name("Hello");
401 assert_eq!(source.name(), "Hello World");
402 }
403
404 #[test]
405 fn binding_to_transform_change_type() {
406 let binding_group = crate::BindingGroup::new();
407
408 let source = TestObject::default();
409 let target = TestObject::default();
410
411 binding_group.set_source(Some(&source));
412 binding_group
413 .bind("name", &target, "enabled")
414 .sync_create()
415 .transform_to(|_binding, value| {
416 let value = value.get::<&str>().unwrap();
417 Some((value == "Hello").to_value())
418 })
419 .transform_from(|_binding, value| {
420 let value = value.get::<bool>().unwrap();
421 Some((if value { "Hello" } else { "World" }).to_value())
422 })
423 .build();
424
425 source.set_name("Hello");
426 assert!(target.enabled());
427
428 source.set_name("Hello World");
429 assert!(!target.enabled());
430 }
431
432 #[test]
433 fn binding_from_transform_change_type() {
434 let binding_group = crate::BindingGroup::new();
435
436 let source = TestObject::default();
437 let target = TestObject::default();
438
439 binding_group.set_source(Some(&source));
440 binding_group
441 .bind("name", &target, "enabled")
442 .sync_create()
443 .bidirectional()
444 .transform_to(|_binding, value| {
445 let value = value.get::<&str>().unwrap();
446 Some((value == "Hello").to_value())
447 })
448 .transform_from(|_binding, value| {
449 let value = value.get::<bool>().unwrap();
450 Some((if value { "Hello" } else { "World" }).to_value())
451 })
452 .build();
453
454 target.set_enabled(true);
455 assert_eq!(source.name(), "Hello");
456 target.set_enabled(false);
457 assert_eq!(source.name(), "World");
458 }
459
460 mod imp {
461 use std::{cell::RefCell, sync::OnceLock};
462
463 use super::*;
464 use crate as glib;
465
466 #[derive(Debug, Default)]
467 pub struct TestObject {
468 pub name: RefCell<String>,
469 pub enabled: RefCell<bool>,
470 }
471
472 #[crate::object_subclass]
473 impl ObjectSubclass for TestObject {
474 const NAME: &'static str = "TestBindingGroup";
475 type Type = super::TestObject;
476 }
477
478 impl ObjectImpl for TestObject {
479 fn properties() -> &'static [crate::ParamSpec] {
480 static PROPERTIES: OnceLock<Vec<crate::ParamSpec>> = OnceLock::new();
481 PROPERTIES.get_or_init(|| {
482 vec![
483 crate::ParamSpecString::builder("name")
484 .explicit_notify()
485 .build(),
486 crate::ParamSpecBoolean::builder("enabled")
487 .explicit_notify()
488 .build(),
489 ]
490 })
491 }
492
493 fn property(&self, _id: usize, pspec: &crate::ParamSpec) -> crate::Value {
494 let obj = self.obj();
495 match pspec.name() {
496 "name" => obj.name().to_value(),
497 "enabled" => obj.enabled().to_value(),
498 _ => unimplemented!(),
499 }
500 }
501
502 fn set_property(&self, _id: usize, value: &crate::Value, pspec: &crate::ParamSpec) {
503 let obj = self.obj();
504 match pspec.name() {
505 "name" => obj.set_name(value.get().unwrap()),
506 "enabled" => obj.set_enabled(value.get().unwrap()),
507 _ => unimplemented!(),
508 };
509 }
510 }
511 }
512
513 crate::wrapper! {
514 pub struct TestObject(ObjectSubclass<imp::TestObject>);
515 }
516
517 impl Default for TestObject {
518 fn default() -> Self {
519 crate::Object::new()
520 }
521 }
522
523 impl TestObject {
524 fn name(&self) -> String {
525 self.imp().name.borrow().clone()
526 }
527
528 fn set_name(&self, name: &str) {
529 if name != self.imp().name.replace(name.to_string()).as_str() {
530 self.notify("name");
531 }
532 }
533
534 fn enabled(&self) -> bool {
535 *self.imp().enabled.borrow()
536 }
537
538 fn set_enabled(&self, enabled: bool) {
539 if enabled != self.imp().enabled.replace(enabled) {
540 self.notify("enabled");
541 }
542 }
543 }
544}