diff --git a/glib/src/object.rs b/glib/src/object.rs index ab60a8cf1b4b..90a72233e22f 100644 --- a/glib/src/object.rs +++ b/glib/src/object.rs @@ -13,7 +13,7 @@ use crate::{ thread_guard::thread_id, translate::*, value::FromValue, - Closure, PtrSlice, RustClosure, SignalHandlerId, Type, Value, + Closure, PtrSlice, RustClosure, SendValue, SignalHandlerId, Type, Value, }; // rustdoc-stripper-ignore-next @@ -1525,9 +1525,92 @@ impl<'a, O: IsA + IsClass> ObjectBuilder<'a, O> { } // rustdoc-stripper-ignore-next - /// Set property `name` to the given value `value`. + /// Build the object with the provided properties. + /// + /// # Panics + /// + /// This panics if the object is not instantiable, doesn't have all the given properties or + /// property values of the wrong type are provided. + #[track_caller] + #[inline] + pub fn build(mut self) -> O { + let object = Object::with_mut_values(self.type_, &mut self.properties); + unsafe { object.unsafe_cast::() } + } +} + +pub trait ObjectBuilderPropertySetter<'a>: Sized { + // rustdoc-stripper-ignore-next + /// Sets property `name` to the given value `value`. + /// + /// Overrides any default or previously defined value. + fn property(self, name: &'a str, value: impl Into) -> Self; + + // 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 is kept. + #[inline] + fn property_if(self, name: &'a str, value: impl Into, predicate: bool) -> Self { + if predicate { + ObjectBuilderPropertySetter::property(self, 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 is kept. + #[inline] + fn property_if_some(self, name: &'a str, value: Option>) -> Self { + if let Some(value) = value { + ObjectBuilderPropertySetter::property(self, 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. + #[inline] + fn property_from_iter + FromIterator>( + self, + name: &'a str, + iter: impl IntoIterator>, + ) -> Self { + let iter = iter.into_iter().map(|item| item.into()); + ObjectBuilderPropertySetter::property(self, 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 is unchanged. #[inline] - pub fn property(self, name: &'a str, value: impl Into) -> Self { + 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()); + ObjectBuilderPropertySetter::property(self, name, V::from_iter(iter)) + } else { + self + } + } +} + +impl<'a, O: IsA + IsClass> ObjectBuilderPropertySetter<'a> for ObjectBuilder<'a, O> { + #[inline] + fn property(self, name: &'a str, value: impl Into) -> Self { let ObjectBuilder { type_, mut properties, @@ -1541,20 +1624,6 @@ impl<'a, O: IsA + IsClass> ObjectBuilder<'a, O> { phantom: PhantomData, } } - - // rustdoc-stripper-ignore-next - /// Build the object with the provided properties. - /// - /// # Panics - /// - /// This panics if the object is not instantiable, doesn't have all the given properties or - /// property values of the wrong type are provided. - #[track_caller] - #[inline] - pub fn build(mut self) -> O { - let object = Object::with_mut_values(self.type_, &mut self.properties); - unsafe { object.unsafe_cast::() } - } } #[must_use = "if unused the property notifications will immediately be thawed"] diff --git a/glib/src/prelude.rs b/glib/src/prelude.rs index cb565ecfbf5c..c980834be834 100644 --- a/glib/src/prelude.rs +++ b/glib/src/prelude.rs @@ -6,7 +6,9 @@ pub use crate::{ error::ErrorDomain, gobject::traits::{DynamicObjectRegisterExt, TypeModuleExt, TypePluginExt}, - object::{Cast, CastNone, IsA, ObjectClassExt, ObjectExt, ObjectType}, + object::{ + Cast, CastNone, IsA, ObjectBuilderPropertySetter, ObjectClassExt, ObjectExt, ObjectType, + }, param_spec::{HasParamSpec, ParamSpecBuilderExt, ParamSpecType}, types::{StaticType, StaticTypeExt}, value::{ToSendValue, ToValue, ValueType}, diff --git a/glib/src/subclass/object.rs b/glib/src/subclass/object.rs index e840e13d8e36..7ae0fda4a2d8 100644 --- a/glib/src/subclass/object.rs +++ b/glib/src/subclass/object.rs @@ -345,11 +345,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] @@ -372,6 +385,10 @@ mod test { .read_only() .build(), crate::ParamSpecObject::builder::("child").build(), + crate::ParamSpecInt::builder("answer") + .default_value(42i32) + .build(), + crate::ParamSpecValueArray::builder("array").build(), ] }) } @@ -432,6 +449,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!(), } } @@ -441,6 +472,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::from_items(&*self.array.borrow()).to_value(), _ => unimplemented!(), } } @@ -511,11 +544,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] @@ -565,6 +600,139 @@ 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::from_items(["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::from_items(["val0", "val1"]), + 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::from_items(["val0", "val1"]))) + .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() { diff --git a/glib/src/value_array.rs b/glib/src/value_array.rs index b7d61be3ad63..035363f0da78 100644 --- a/glib/src/value_array.rs +++ b/glib/src/value_array.rs @@ -3,7 +3,8 @@ use std::{cmp::Ordering, ops, slice}; use crate::{ - prelude::*, translate::*, ParamSpecValueArray, ParamSpecValueArrayBuilder, Type, Value, + prelude::*, translate::*, ParamSpecValueArray, ParamSpecValueArrayBuilder, SendValue, Type, + Value, }; wrapper! { @@ -23,6 +24,25 @@ impl ValueArray { unsafe { from_glib_full(gobject_ffi::g_value_array_new(n_prealloced)) } } + pub fn from_items(values: impl IntoIterator + Send>) -> Self { + unsafe { + let values = values.into_iter(); + let array = gobject_ffi::g_value_array_new(values.size_hint().0 as u32); + + for v in values.into_iter() { + let v = v.into(); + let v = v.to_glib_none(); + gobject_ffi::g_value_array_append(array, v.0); + } + + from_glib_full(array) + } + } + + pub fn from_values(values: impl IntoIterator) -> Self { + Self::from_items(values) + } + #[doc(alias = "g_value_array_append")] pub fn append(&mut self, value: &Value) { let value = value.to_glib_none(); @@ -244,3 +264,9 @@ impl HasParamSpec for ValueArray { Self::ParamSpec::builder } } + +impl std::iter::FromIterator for ValueArray { + fn from_iter>(values: T) -> Self { + Self::from_values(values) + } +}