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