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