Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ObjectBuilder: add property_if(), property_if_some(), property_from_iter() ... #1377

Merged
merged 1 commit into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 64 additions & 2 deletions glib/src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1542,8 +1542,9 @@ impl<'a, O: IsA<Object> + 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<Value>) -> Self {
let ObjectBuilder {
type_,
Expand All @@ -1559,6 +1560,67 @@ impl<'a, O: IsA<Object> + 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<Value>, 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<impl Into<Value>>) -> 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<V: ValueType + Into<Value> + FromIterator<Value>>(
self,
name: &'a str,
iter: impl IntoIterator<Item = impl Into<Value>>,
) -> 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<V: ValueType + Into<Value> + FromIterator<Value>>(
self,
name: &'a str,
iter: impl IntoIterator<Item = impl Into<Value>>,
) -> 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.
///
Expand Down
164 changes: 162 additions & 2 deletions glib/src/subclass/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,11 +350,24 @@ mod test {

impl ObjectImpl for ChildObject {}

#[derive(Default)]
pub struct SimpleObject {
name: RefCell<Option<String>>,
construct_name: RefCell<Option<String>>,
constructed: RefCell<bool>,
answer: RefCell<i32>,
array: RefCell<Vec<String>>,
}

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]
Expand All @@ -377,6 +390,10 @@ mod test {
.read_only()
.build(),
crate::ParamSpecObject::builder::<super::ChildObject>("child").build(),
crate::ParamSpecInt::builder("answer")
.default_value(42i32)
.build(),
crate::ParamSpecValueArray::builder("array").build(),
]
})
}
Expand Down Expand Up @@ -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::<crate::ValueArray>()
.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!(),
}
}
Expand All @@ -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!(),
}
}
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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::<SimpleObject>()
.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::<String>("name").as_str(), "some name");
assert_eq!(
obj.property::<Option<String>>("name").as_deref(),
Some("some name")
);
assert_eq!(obj.property::<i32>("answer"), 21);
assert!(obj
.property::<ValueArray>("array")
.iter()
.map(|val| val.get::<&str>().unwrap())
.eq(array));

let obj = Object::builder::<SimpleObject>()
.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::<Option<String>>("name").is_none());
assert_eq!(obj.property::<i32>("answer"), 42);
assert!(obj
.property::<ValueArray>("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::<SimpleObject>()
.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::<String>("name").as_str(), "some name");
assert_eq!(
obj.property::<Option<String>>("name").as_deref(),
Some("some name")
);
assert_eq!(obj.property::<i32>("answer"), 21);
assert!(obj
.property::<ValueArray>("array")
.iter()
.map(|val| val.get::<&str>().unwrap())
.eq(array));

let obj = Object::builder::<SimpleObject>()
.property_if_some("name", Option::<&str>::None)
.property_if_some("answer", Option::<i32>::None)
.property_if_some("array", Option::<ValueArray>::None)
.build();

assert!(obj.property::<Option<String>>("name").is_none());
assert_eq!(obj.property::<i32>("answer"), 42);
assert!(obj
.property::<ValueArray>("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::<SimpleObject>()
.property_from_iter::<ValueArray>("array", &array)
.build();

assert!(obj
.property::<ValueArray>("array")
.iter()
.map(|val| val.get::<&str>().unwrap())
.eq(array));

let obj = Object::builder::<SimpleObject>()
.property_from_iter::<ValueArray>("array", Vec::<&str>::new())
.build();

assert!(obj.property::<ValueArray>("array").is_empty());
}

#[test]
fn builder_property_if_not_empty() {
use crate::ValueArray;

let array = ["val0", "val1"];
let obj = Object::builder::<SimpleObject>()
.property_if_not_empty::<ValueArray>("array", &array)
.build();

assert!(obj
.property::<ValueArray>("array")
.iter()
.map(|val| val.get::<&str>().unwrap())
.eq(array));

let empty_vec = Vec::<String>::new();
let obj = Object::builder::<SimpleObject>()
.property_if_not_empty::<ValueArray>("array", &empty_vec)
.build();

assert!(obj
.property::<ValueArray>("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() {
Expand Down
Loading