diff --git a/glib/src/object.rs b/glib/src/object.rs index c62ea8449ab1..47ac2755763c 100644 --- a/glib/src/object.rs +++ b/glib/src/object.rs @@ -1542,8 +1542,9 @@ impl<'a, O: IsA + IsClass> ObjectBuilder<'a, O> { } // rustdoc-stripper-ignore-next - /// Set property `name` to the given value `value`. - #[inline] + /// Sets property `name` to the given value `value`. + /// + /// Overrides any default or previously defined value for `name`. pub fn property(self, name: &'a str, value: impl Into) -> Self { let ObjectBuilder { type_, @@ -1559,6 +1560,67 @@ impl<'a, O: IsA + IsClass> ObjectBuilder<'a, O> { } } + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given inner value if the `predicate` evaluates to `true`. + /// + /// This has no effect if the `predicate` evaluates to `false`, + /// i.e. default or previous value for `name` is kept. + #[inline] + pub fn property_if(self, name: &'a str, value: impl Into, predicate: bool) -> Self { + if predicate { + self.property(name, value) + } else { + self + } + } + + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given inner value if `value` is `Some`. + /// + /// This has no effect if the value is `None`, i.e. default or previous value for `name` is kept. + #[inline] + pub fn property_if_some(self, name: &'a str, value: Option>) -> Self { + if let Some(value) = value { + self.property(name, value) + } else { + self + } + } + + // rustdoc-stripper-ignore-next + /// Sets property `name` using the given `ValueType` `V` built from `iter`'s the `Item`s. + /// + /// Overrides any default or previously defined value for `name`. + #[inline] + pub fn property_from_iter + FromIterator>( + self, + name: &'a str, + iter: impl IntoIterator>, + ) -> Self { + let iter = iter.into_iter().map(|item| item.into()); + self.property(name, V::from_iter(iter)) + } + + // rustdoc-stripper-ignore-next + /// Sets property `name` using the given `ValueType` `V` built from `iter`'s Item`s, + /// if `iter` is not empty. + /// + /// This has no effect if `iter` is empty, i.e. previous property value for `name` is unchanged. + #[inline] + pub fn property_if_not_empty + FromIterator>( + self, + name: &'a str, + iter: impl IntoIterator>, + ) -> Self { + let mut iter = iter.into_iter().peekable(); + if iter.peek().is_some() { + let iter = iter.map(|item| item.into()); + self.property(name, V::from_iter(iter)) + } else { + self + } + } + // rustdoc-stripper-ignore-next /// Build the object with the provided properties. /// diff --git a/glib/src/subclass/object.rs b/glib/src/subclass/object.rs index 8e55cf248cf2..ae84380a48f7 100644 --- a/glib/src/subclass/object.rs +++ b/glib/src/subclass/object.rs @@ -350,11 +350,24 @@ mod test { impl ObjectImpl for ChildObject {} - #[derive(Default)] pub struct SimpleObject { name: RefCell>, construct_name: RefCell>, constructed: RefCell, + answer: RefCell, + array: RefCell>, + } + + impl Default for SimpleObject { + fn default() -> Self { + SimpleObject { + name: Default::default(), + construct_name: Default::default(), + constructed: Default::default(), + answer: RefCell::new(42i32), + array: RefCell::new(vec!["default0".to_string(), "default1".to_string()]), + } + } } #[glib::object_subclass] @@ -377,6 +390,10 @@ mod test { .read_only() .build(), crate::ParamSpecObject::builder::("child").build(), + crate::ParamSpecInt::builder("answer") + .default_value(42i32) + .build(), + crate::ParamSpecValueArray::builder("array").build(), ] }) } @@ -437,6 +454,20 @@ mod test { "child" => { // not stored, only used to test `set_property` with `Objects` } + "answer" => { + let answer = value + .get() + .expect("type conformity checked by 'Object::set_property'"); + self.answer.replace(answer); + } + "array" => { + let value = value + .get::() + .expect("type conformity checked by 'Object::set_property'"); + let mut array = self.array.borrow_mut(); + array.clear(); + array.extend(value.iter().map(|v| v.get().unwrap())); + } _ => unimplemented!(), } } @@ -446,6 +477,8 @@ mod test { "name" => self.name.borrow().to_value(), "construct-name" => self.construct_name.borrow().to_value(), "constructed" => self.constructed.borrow().to_value(), + "answer" => self.answer.borrow().to_value(), + "array" => crate::ValueArray::new(self.array.borrow().iter()).to_value(), _ => unimplemented!(), } } @@ -523,11 +556,13 @@ mod test { assert!(obj.type_().is_a(Dummy::static_type())); let properties = obj.list_properties(); - assert_eq!(properties.len(), 4); + assert_eq!(properties.len(), 6); assert_eq!(properties[0].name(), "name"); assert_eq!(properties[1].name(), "construct-name"); assert_eq!(properties[2].name(), "constructed"); assert_eq!(properties[3].name(), "child"); + assert_eq!(properties[4].name(), "answer"); + assert_eq!(properties[5].name(), "array"); } #[test] @@ -577,6 +612,131 @@ mod test { obj.set_property("child", &child); } + #[test] + fn builder_property_if() { + use crate::ValueArray; + + let array = ["val0", "val1"]; + let obj = Object::builder::() + .property_if("name", "some name", true) + .property_if("answer", 21i32, 6 != 9) + .property_if("array", ValueArray::new(["val0", "val1"]), array.len() == 2) + .build(); + + assert_eq!(obj.property::("name").as_str(), "some name"); + assert_eq!( + obj.property::>("name").as_deref(), + Some("some name") + ); + assert_eq!(obj.property::("answer"), 21); + assert!(obj + .property::("array") + .iter() + .map(|val| val.get::<&str>().unwrap()) + .eq(array)); + + let obj = Object::builder::() + .property_if("name", "some name", false) + .property_if("answer", 21i32, 6 == 9) + .property_if("array", ValueArray::new(array), array.len() == 4) + .build(); + + assert!(obj.property::>("name").is_none()); + assert_eq!(obj.property::("answer"), 42); + assert!(obj + .property::("array") + .iter() + .map(|val| val.get::<&str>().unwrap()) + .eq(["default0", "default1"])); + } + + #[test] + fn builder_property_if_some() { + use crate::ValueArray; + + let array = ["val0", "val1"]; + let obj = Object::builder::() + .property_if_some("name", Some("some name")) + .property_if_some("answer", Some(21i32)) + .property_if_some("array", Some(ValueArray::new(array))) + .build(); + + assert_eq!(obj.property::("name").as_str(), "some name"); + assert_eq!( + obj.property::>("name").as_deref(), + Some("some name") + ); + assert_eq!(obj.property::("answer"), 21); + assert!(obj + .property::("array") + .iter() + .map(|val| val.get::<&str>().unwrap()) + .eq(array)); + + let obj = Object::builder::() + .property_if_some("name", Option::<&str>::None) + .property_if_some("answer", Option::::None) + .property_if_some("array", Option::::None) + .build(); + + assert!(obj.property::>("name").is_none()); + assert_eq!(obj.property::("answer"), 42); + assert!(obj + .property::("array") + .iter() + .map(|val| val.get::<&str>().unwrap()) + .eq(["default0", "default1"])); + } + + #[test] + fn builder_property_from_iter() { + use crate::ValueArray; + + let array = ["val0", "val1"]; + let obj = Object::builder::() + .property_from_iter::("array", &array) + .build(); + + assert!(obj + .property::("array") + .iter() + .map(|val| val.get::<&str>().unwrap()) + .eq(array)); + + let obj = Object::builder::() + .property_from_iter::("array", Vec::<&str>::new()) + .build(); + + assert!(obj.property::("array").is_empty()); + } + + #[test] + fn builder_property_if_not_empty() { + use crate::ValueArray; + + let array = ["val0", "val1"]; + let obj = Object::builder::() + .property_if_not_empty::("array", &array) + .build(); + + assert!(obj + .property::("array") + .iter() + .map(|val| val.get::<&str>().unwrap()) + .eq(array)); + + let empty_vec = Vec::::new(); + let obj = Object::builder::() + .property_if_not_empty::("array", &empty_vec) + .build(); + + assert!(obj + .property::("array") + .iter() + .map(|val| val.get::<&str>().unwrap()) + .eq(["default0", "default1"])); + } + #[test] #[should_panic = "property 'construct-name' of type 'SimpleObject' is not writable"] fn test_set_property_non_writable() {