all: GObject builders: generalise property_from_str

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1594>
This commit is contained in:
François Laignel 2024-11-19 10:11:32 +01:00
parent 68571f6f75
commit 9c386085e3
8 changed files with 551 additions and 237 deletions

View file

@ -512,9 +512,13 @@ impl AppSink {
/// Creates a new builder-pattern struct instance to construct [`AppSink`] objects. /// Creates a new builder-pattern struct instance to construct [`AppSink`] objects.
/// ///
/// This method returns an instance of [`AppSinkBuilder`](crate::builders::AppSinkBuilder) which can be used to create [`AppSink`] objects. /// This method returns an instance of [`AppSinkBuilder`](crate::builders::AppSinkBuilder) which can be used to create [`AppSink`] objects.
pub fn builder() -> AppSinkBuilder { pub fn builder<'a>() -> AppSinkBuilder<'a> {
assert_initialized_main_thread!(); assert_initialized_main_thread!();
AppSinkBuilder::new() AppSinkBuilder {
builder: gst::Object::builder(),
callbacks: None,
drop_out_of_segment: None,
}
} }
#[doc(alias = "gst_app_sink_set_callbacks")] #[doc(alias = "gst_app_sink_set_callbacks")]
@ -1173,26 +1177,23 @@ impl AppSink {
/// ///
/// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
#[must_use = "The builder must be built to be used"] #[must_use = "The builder must be built to be used"]
pub struct AppSinkBuilder { pub struct AppSinkBuilder<'a> {
builder: glib::object::ObjectBuilder<'static, AppSink>, builder: gst::gobject::GObjectBuilder<'a, AppSink>,
callbacks: Option<AppSinkCallbacks>, callbacks: Option<AppSinkCallbacks>,
drop_out_of_segment: Option<bool>, drop_out_of_segment: Option<bool>,
} }
impl AppSinkBuilder { impl<'a> AppSinkBuilder<'a> {
fn new() -> Self {
Self {
builder: glib::Object::builder(),
callbacks: None,
drop_out_of_segment: None,
}
}
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Build the [`AppSink`]. /// Build the [`AppSink`].
///
/// # Panics
///
/// This panics if the [`AppSink`] doesn't have all the given properties or
/// property values of the wrong type are provided.
#[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"] #[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"]
pub fn build(self) -> AppSink { pub fn build(self) -> AppSink {
let appsink = self.builder.build(); let appsink = self.builder.build().unwrap();
if let Some(callbacks) = self.callbacks { if let Some(callbacks) = self.callbacks {
appsink.set_callbacks(callbacks); appsink.set_callbacks(callbacks);
@ -1226,7 +1227,7 @@ impl AppSinkBuilder {
} }
} }
pub fn caps(self, caps: &gst::Caps) -> Self { pub fn caps(self, caps: &'a gst::Caps) -> Self {
Self { Self {
builder: self.builder.property("caps", caps), builder: self.builder.property("caps", caps),
..self ..self
@ -1350,12 +1351,29 @@ impl AppSinkBuilder {
} }
} }
pub fn name(self, name: impl Into<glib::GString>) -> Self { // rustdoc-stripper-ignore-next
/// Sets property `name` to the given value `value`.
///
/// Overrides any default or previously defined value for `name`.
#[inline]
pub fn property(self, name: &'a str, value: impl Into<glib::Value> + 'a) -> Self {
Self { Self {
builder: self.builder.property("name", name.into()), builder: self.builder.property(name, value),
..self ..self
} }
} }
// rustdoc-stripper-ignore-next
/// Sets property `name` to the given string value `value`.
#[inline]
pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self {
Self {
builder: self.builder.property_from_str(name, value),
..self
}
}
gst::impl_builder_gvalue_extra_setters!(property_and_name);
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -311,9 +311,13 @@ impl AppSrc {
/// Creates a new builder-pattern struct instance to construct [`AppSrc`] objects. /// Creates a new builder-pattern struct instance to construct [`AppSrc`] objects.
/// ///
/// This method returns an instance of [`AppSrcBuilder`](crate::builders::AppSrcBuilder) which can be used to create [`AppSrc`] objects. /// This method returns an instance of [`AppSrcBuilder`](crate::builders::AppSrcBuilder) which can be used to create [`AppSrc`] objects.
pub fn builder() -> AppSrcBuilder { pub fn builder<'a>() -> AppSrcBuilder<'a> {
assert_initialized_main_thread!(); assert_initialized_main_thread!();
AppSrcBuilder::new() AppSrcBuilder {
builder: gst::Object::builder(),
callbacks: None,
automatic_eos: None,
}
} }
#[doc(alias = "gst_app_src_set_callbacks")] #[doc(alias = "gst_app_src_set_callbacks")]
@ -453,26 +457,23 @@ impl AppSrc {
/// ///
/// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
#[must_use = "The builder must be built to be used"] #[must_use = "The builder must be built to be used"]
pub struct AppSrcBuilder { pub struct AppSrcBuilder<'a> {
builder: glib::object::ObjectBuilder<'static, AppSrc>, builder: gst::gobject::GObjectBuilder<'a, AppSrc>,
callbacks: Option<AppSrcCallbacks>, callbacks: Option<AppSrcCallbacks>,
automatic_eos: Option<bool>, automatic_eos: Option<bool>,
} }
impl AppSrcBuilder { impl<'a> AppSrcBuilder<'a> {
fn new() -> Self {
Self {
builder: glib::Object::builder(),
callbacks: None,
automatic_eos: None,
}
}
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Build the [`AppSrc`]. /// Build the [`AppSrc`].
///
/// # Panics
///
/// This panics if the [`AppSrc`] doesn't have all the given properties or
/// property values of the wrong type are provided.
#[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"] #[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"]
pub fn build(self) -> AppSrc { pub fn build(self) -> AppSrc {
let appsrc = self.builder.build(); let appsrc = self.builder.build().unwrap();
if let Some(callbacks) = self.callbacks { if let Some(callbacks) = self.callbacks {
appsrc.set_callbacks(callbacks); appsrc.set_callbacks(callbacks);
@ -506,7 +507,7 @@ impl AppSrcBuilder {
} }
} }
pub fn caps(self, caps: &gst::Caps) -> Self { pub fn caps(self, caps: &'a gst::Caps) -> Self {
Self { Self {
builder: self.builder.property("caps", caps), builder: self.builder.property("caps", caps),
..self ..self
@ -621,12 +622,29 @@ impl AppSrcBuilder {
} }
} }
pub fn name(self, name: impl Into<glib::GString>) -> Self { // rustdoc-stripper-ignore-next
/// Sets property `name` to the given value `value`.
///
/// Overrides any default or previously defined value for `name`.
#[inline]
pub fn property(self, name: &'a str, value: impl Into<glib::Value> + 'a) -> Self {
Self { Self {
builder: self.builder.property("name", name.into()), builder: self.builder.property(name, value),
..self ..self
} }
} }
// rustdoc-stripper-ignore-next
/// Sets property `name` to the given string value `value`.
#[inline]
pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self {
Self {
builder: self.builder.property_from_str(name, value),
..self
}
}
gst::impl_builder_gvalue_extra_setters!(property_and_name);
} }
#[derive(Debug)] #[derive(Debug)]
@ -792,4 +810,37 @@ mod tests {
sample_quantity sample_quantity
); );
} }
#[test]
fn builder_caps_lt() {
gst::init().unwrap();
let caps = &gst::Caps::new_any();
{
let stream_type = "random-access".to_owned();
let appsrc = AppSrc::builder()
.property_from_str("stream-type", &stream_type)
.caps(caps)
.build();
assert_eq!(
appsrc.property::<crate::AppStreamType>("stream-type"),
crate::AppStreamType::RandomAccess
);
assert!(appsrc.property::<gst::Caps>("caps").is_any());
}
let stream_type = &"random-access".to_owned();
{
let caps = &gst::Caps::new_any();
let appsrc = AppSrc::builder()
.property_from_str("stream-type", stream_type)
.caps(caps)
.build();
assert_eq!(
appsrc.property::<crate::AppStreamType>("stream-type"),
crate::AppStreamType::RandomAccess
);
assert!(appsrc.property::<gst::Caps>("caps").is_any());
}
}
} }

View file

@ -37,8 +37,11 @@ impl Bin {
/// Creates a new builder-pattern struct instance to construct [`Bin`] objects. /// Creates a new builder-pattern struct instance to construct [`Bin`] objects.
/// ///
/// This method returns an instance of [`BinBuilder`] which can be used to create [`Bin`] objects. /// This method returns an instance of [`BinBuilder`] which can be used to create [`Bin`] objects.
pub fn builder() -> BinBuilder { pub fn builder<'a>() -> BinBuilder<'a> {
BinBuilder::new() assert_initialized_main_thread!();
BinBuilder {
builder: crate::Object::builder(),
}
} }
} }
@ -221,22 +224,21 @@ impl Default for Bin {
/// ///
/// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
#[must_use = "The builder must be built to be used"] #[must_use = "The builder must be built to be used"]
pub struct BinBuilder { pub struct BinBuilder<'a> {
builder: glib::object::ObjectBuilder<'static, Bin>, builder: crate::gobject::GObjectBuilder<'a, Bin>,
}
impl BinBuilder {
fn new() -> Self {
Self {
builder: glib::Object::builder(),
}
} }
impl<'a> BinBuilder<'a> {
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Build the [`Bin`]. /// Build the [`Bin`].
///
/// # Panics
///
/// This panics if the [`Bin`] doesn't have all the given properties or
/// property values of the wrong type are provided.
#[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"] #[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"]
pub fn build(self) -> Bin { pub fn build(self) -> Bin {
self.builder.build() self.builder.build().unwrap()
} }
pub fn async_handling(self, async_handling: bool) -> Self { pub fn async_handling(self, async_handling: bool) -> Self {
@ -267,27 +269,27 @@ impl BinBuilder {
} }
} }
pub fn name(self, name: impl Into<glib::GString>) -> Self { // rustdoc-stripper-ignore-next
/// Sets property `name` to the given value `value`.
///
/// Overrides any default or previously defined value for `name`.
#[inline]
pub fn property(self, name: &'a str, value: impl Into<glib::Value> + 'a) -> Self {
Self { Self {
builder: self.builder.property("name", name.into()), builder: self.builder.property(name, value),
} }
} }
pub fn name_if(self, name: impl Into<glib::GString>, predicate: bool) -> Self { // rustdoc-stripper-ignore-next
if predicate { /// Sets property `name` to the given string value `value`.
self.name(name) #[inline]
} else { pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self {
self Self {
builder: self.builder.property_from_str(name, value),
} }
} }
pub fn name_if_some(self, name: Option<impl Into<glib::GString>>) -> Self { impl_builder_gvalue_extra_setters!(property_and_name);
if let Some(name) = name {
self.name(name)
} else {
self
}
}
} }
unsafe extern "C" fn do_latency_trampoline< unsafe extern "C" fn do_latency_trampoline<
@ -346,4 +348,20 @@ mod tests {
vec![String::from("identity0"), String::from("identity1")] vec![String::from("identity0"), String::from("identity1")]
); );
} }
#[test]
fn builder() {
crate::init().unwrap();
let msg_fwd = "message-forward";
let bin = Bin::builder()
.name("test-bin")
.property("async-handling", true)
.property_from_str(msg_fwd, "True")
.build();
assert_eq!(bin.name(), "test-bin");
assert!(bin.property::<bool>("async-handling"));
assert!(bin.property::<bool>("message-forward"));
}
} }

View file

@ -16,10 +16,9 @@ impl ElementFactory {
#[track_caller] #[track_caller]
pub fn create(&self) -> ElementBuilder { pub fn create(&self) -> ElementBuilder {
assert_initialized_main_thread!(); assert_initialized_main_thread!();
ElementBuilder { ElementBuilder {
name_or_factory: NameOrFactory::Factory(self), name_or_factory: NameOrFactory::Factory(self),
properties: smallvec::SmallVec::new(), builder: crate::Object::builder_for_deferred_type(),
} }
} }
@ -28,10 +27,9 @@ impl ElementFactory {
#[track_caller] #[track_caller]
pub fn make(factoryname: &str) -> ElementBuilder { pub fn make(factoryname: &str) -> ElementBuilder {
assert_initialized_main_thread!(); assert_initialized_main_thread!();
ElementBuilder { ElementBuilder {
name_or_factory: NameOrFactory::Name(factoryname), name_or_factory: NameOrFactory::Name(factoryname),
properties: smallvec::SmallVec::new(), builder: crate::Object::builder_for_deferred_type(),
} }
} }
@ -190,7 +188,7 @@ impl ElementFactory {
#[must_use = "The builder must be built to be used"] #[must_use = "The builder must be built to be used"]
pub struct ElementBuilder<'a> { pub struct ElementBuilder<'a> {
name_or_factory: NameOrFactory<'a>, name_or_factory: NameOrFactory<'a>,
properties: smallvec::SmallVec<[(&'a str, ValueOrStr<'a>); 16]>, builder: crate::gobject::GObjectBuilder<'a, Element>,
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -199,30 +197,7 @@ enum NameOrFactory<'a> {
Factory(&'a ElementFactory), Factory(&'a ElementFactory),
} }
enum ValueOrStr<'a> {
Value(glib::Value),
Str(&'a str),
}
impl<'a> ElementBuilder<'a> { impl<'a> ElementBuilder<'a> {
// rustdoc-stripper-ignore-next
/// Sets the name property to the given `name`.
#[inline]
pub fn name(self, name: impl Into<glib::GString>) -> Self {
self.property("name", name.into())
}
// rustdoc-stripper-ignore-next
/// Sets the name property to the given `name` if it is `Some`.
#[inline]
pub fn name_if_some(self, name: Option<impl Into<glib::GString>>) -> Self {
if let Some(name) = name {
self.name(name)
} else {
self
}
}
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Sets property `name` to the given value `value`. /// Sets property `name` to the given value `value`.
/// ///
@ -230,51 +205,34 @@ impl<'a> ElementBuilder<'a> {
#[inline] #[inline]
pub fn property(self, name: &'a str, value: impl Into<glib::Value> + 'a) -> Self { pub fn property(self, name: &'a str, value: impl Into<glib::Value> + 'a) -> Self {
Self { Self {
name_or_factory: self.name_or_factory, builder: self.builder.property(name, value),
properties: { ..self
let mut properties = self.properties;
properties.push((name, ValueOrStr::Value(value.into())));
properties
},
} }
} }
impl_builder_gvalue_extra_setters!(property);
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Sets property `name` to the given string value `value`. /// Sets property `name` to the given string value `value`.
#[inline] #[inline]
pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self { pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self {
Self { Self {
name_or_factory: self.name_or_factory, builder: self.builder.property_from_str(name, value),
properties: { ..self
let mut properties = self.properties;
properties.push((name, ValueOrStr::Str(value)));
properties
},
} }
} }
// rustdoc-stripper-ignore-next impl_builder_gvalue_extra_setters!(property_and_name);
/// Sets property `name` to the given string value `value` if it is `Some`.
#[inline]
pub fn property_from_str_if_some(self, name: &'a str, value: Option<&'a str>) -> Self {
if let Some(value) = value {
self.property_from_str(name, value)
} else {
self
}
}
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Builds the element with the provided properties. /// Builds the [`Element`] with the provided properties.
/// ///
/// This fails if there is no such element factory or the element factory can't be loaded. /// This fails if there is no such [`ElementFactory`] or the [`ElementFactory`] can't be loaded.
/// ///
/// # Panics /// # Panics
/// ///
/// This panics if the element is not instantiable, doesn't have all the given properties or /// This panics if the [`Element`] is not instantiable, doesn't have all the given properties or
/// property values of the wrong type are provided. /// property values of the wrong type are provided.
///
/// [`Element`]: crate::Element
#[track_caller] #[track_caller]
#[must_use = "Building the element without using it has no effect"] #[must_use = "Building the element without using it has no effect"]
pub fn build(self) -> Result<Element, glib::BoolError> { pub fn build(self) -> Result<Element, glib::BoolError> {
@ -328,68 +286,21 @@ impl<'a> ElementBuilder<'a> {
)); ));
} }
let mut properties = smallvec::SmallVec::<[_; 16]>::with_capacity(self.properties.len()); let element = self
let klass = glib::Class::<Element>::from_type(element_type).unwrap(); .builder
for (name, value) in self.properties { .type_(element_type)
match value { .build()
ValueOrStr::Value(value) => { .map_err(|err| {
properties.push((name, value)); use crate::gobject::GObjectError::*;
match err {
PropertyNotFound { property, .. } => {
format!("property '{property}' of element factory '{}' not found", factory.name())
},
PropertyFromStr { property, value, .. } => {
format!("property '{property}' of element factory '{}' can't be set from string '{value}'", factory.name())
},
} }
ValueOrStr::Str(value) => { }).unwrap();
use crate::value::GstValueExt;
let pspec = match klass.find_property(name) {
Some(pspec) => pspec,
None => {
panic!(
"property '{}' of element factory '{}' not found",
name,
factory.name()
);
}
};
let value = {
if pspec.value_type() == crate::Structure::static_type() && value == "NULL"
{
None::<crate::Structure>.to_value()
} else {
#[cfg(feature = "v1_20")]
{
glib::Value::deserialize_with_pspec(value, &pspec)
.unwrap_or_else(|_| {
panic!(
"property '{}' of element factory '{}' can't be set from string '{}'",
name,
factory.name(),
value,
)
})
}
#[cfg(not(feature = "v1_20"))]
{
glib::Value::deserialize(value, pspec.value_type())
.unwrap_or_else(|_| {
panic!(
"property '{}' of element factory '{}' can't be set from string '{}'",
name,
factory.name(),
value,
)
})
}
}
};
properties.push((name, value));
}
}
}
let element = unsafe {
glib::Object::with_mut_values(element_type, &mut properties)
.unsafe_cast::<crate::Element>()
};
unsafe { unsafe {
use std::sync::atomic; use std::sync::atomic;
@ -430,3 +341,27 @@ impl<'a> ElementBuilder<'a> {
Ok(element) Ok(element)
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
#[test]
fn builder() {
crate::init().unwrap();
let fakesink = ElementFactory::make("fakesink")
.name("test-fakesink")
.property("can-activate-pull", true)
.property_from_str("state-error", "ready-to-paused")
.build()
.unwrap();
assert_eq!(fakesink.name(), "test-fakesink");
assert!(fakesink.property::<bool>("can-activate-pull"));
let v = fakesink.property_value("state-error");
let (_klass, e) = glib::EnumValue::from_value(&v).unwrap();
assert_eq!(e.nick(), "ready-to-paused");
}
}

View file

@ -1,8 +1,98 @@
// Take a look at the license at the top of the repository in the LICENSE file. // Take a look at the license at the top of the repository in the LICENSE file.
use glib::prelude::*; use std::marker::PhantomData;
use crate::value::GstValueExt; use glib::{object::IsClass, prelude::*, Type};
use crate::{value::GstValueExt, IdStr};
impl crate::Object {
// rustdoc-stripper-ignore-next
/// Builds a `GObjectBuilder` targeting type `O`.
#[inline]
pub fn builder<'a, O>() -> GObjectBuilder<'a, O>
where
O: IsA<crate::Object> + IsClass,
{
assert_initialized_main_thread!();
GObjectBuilder {
type_: Some(O::static_type()),
properties: smallvec::SmallVec::new(),
phantom: PhantomData,
}
}
// rustdoc-stripper-ignore-next
/// Builds a `GObjectBuilder` targeting base class of type `O` and concrete `type_`.
#[inline]
pub fn builder_for<'a, O>(type_: Type) -> GObjectBuilder<'a, O>
where
O: IsA<crate::Object> + IsClass,
{
assert_initialized_main_thread!();
GObjectBuilder {
type_: Some(type_),
properties: smallvec::SmallVec::new(),
phantom: PhantomData,
}
}
// rustdoc-stripper-ignore-next
/// Builds a `GObjectBuilder` targeting base class of type `O`
/// and a concrete `Type` that will be specified later.
///
/// This is useful when the concrete type of the object is dynamically determined
/// when calling the `build()` method of a wrapping builder.
#[inline]
pub fn builder_for_deferred_type<'a, O>() -> GObjectBuilder<'a, O>
where
O: IsA<crate::Object> + IsClass,
{
assert_initialized_main_thread!();
GObjectBuilder {
type_: None,
properties: smallvec::SmallVec::new(),
phantom: PhantomData,
}
}
}
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
pub enum GObjectError {
#[error("property {property} for type {type_} not found")]
PropertyNotFound { type_: Type, property: IdStr },
#[error("property {property} for type {type_} can't be set from string {value}")]
PropertyFromStr {
type_: Type,
property: IdStr,
value: IdStr,
},
}
fn value_from_property_str(
pspec: glib::ParamSpec,
value: &str,
) -> Result<glib::Value, GObjectError> {
skip_assert_initialized!(); // Already checked transitively by caller
if pspec.value_type() == crate::Structure::static_type() && value == "NULL" {
Ok(None::<crate::Structure>.to_value())
} else {
cfg_if::cfg_if! {
if #[cfg(feature = "v1_20")] {
let res = glib::Value::deserialize_with_pspec(value, &pspec);
} else {
let res = glib::Value::deserialize(value, pspec.value_type());
}
}
res.map_err(|_| GObjectError::PropertyFromStr {
type_: pspec.owner_type(),
property: pspec.name().into(),
value: value.into(),
})
}
}
pub trait GObjectExtManualGst: IsA<glib::Object> + 'static { pub trait GObjectExtManualGst: IsA<glib::Object> + 'static {
#[doc(alias = "gst_util_set_object_arg")] #[doc(alias = "gst_util_set_object_arg")]
@ -12,53 +102,192 @@ pub trait GObjectExtManualGst: IsA<glib::Object> + 'static {
panic!("property '{}' of type '{}' not found", name, self.type_()); panic!("property '{}' of type '{}' not found", name, self.type_());
}); });
let value = { self.set_property(name, value_from_property_str(pspec, value).unwrap())
if pspec.value_type() == crate::Structure::static_type() && value == "NULL" {
None::<crate::Structure>.to_value()
} else {
#[cfg(feature = "v1_20")]
{
glib::Value::deserialize_with_pspec(value, &pspec).unwrap_or_else(|_| {
panic!(
"property '{}' of type '{}' can't be set from string '{}'",
name,
self.type_(),
value,
)
})
}
#[cfg(not(feature = "v1_20"))]
{
glib::Value::deserialize(value, pspec.value_type()).unwrap_or_else(|_| {
panic!(
"property '{}' of type '{}' can't be set from string '{}'",
name,
self.type_(),
value,
)
})
}
}
};
self.set_property(name, value)
} }
} }
impl<O: IsA<glib::Object>> GObjectExtManualGst for O {} impl<O: IsA<glib::Object>> GObjectExtManualGst for O {}
// rustdoc-stripper-ignore-next
/// Builder for `GObject`s.
#[must_use = "The builder must be built to be used"]
pub struct GObjectBuilder<'a, O> {
type_: Option<Type>,
properties: smallvec::SmallVec<[(&'a str, ValueOrStr<'a>); 16]>,
phantom: PhantomData<O>,
}
enum ValueOrStr<'a> {
Value(glib::Value),
Str(&'a str),
}
impl<'a, O: IsA<crate::Object> + IsClass> GObjectBuilder<'a, O> {
// rustdoc-stripper-ignore-next
/// Sets the concrete `Type`.
///
/// This should be used on an `GObjectBuilder` created with
/// [`GObjectBuilder::for_deferred_type`].
#[inline]
pub fn type_(mut self, type_: Type) -> Self {
self.type_ = Some(type_);
self
}
// rustdoc-stripper-ignore-next
/// Sets property `name` to the given value `value`.
///
/// Overrides any default or previously defined value for `name`.
#[inline]
pub fn property(self, name: &'a str, value: impl Into<glib::Value> + 'a) -> Self {
Self {
properties: {
let mut properties = self.properties;
properties.push((name, ValueOrStr::Value(value.into())));
properties
},
..self
}
}
// rustdoc-stripper-ignore-next
/// Sets property `name` to the given string value `value`.
#[inline]
pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self {
Self {
properties: {
let mut properties = self.properties;
properties.push((name, ValueOrStr::Str(value)));
properties
},
..self
}
}
impl_builder_gvalue_extra_setters!(property_and_name);
// rustdoc-stripper-ignore-next
/// Builds the [`Object`] with the provided properties.
///
/// This fails if there is no such element factory or the element factory can't be loaded.
///
/// # 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.
/// * The [`GObjectBuilder`] was created for a deferred concrete `Type` but
/// the `Type` was not set.
///
/// [`Object`]: crate::Object
#[track_caller]
#[must_use = "Building the element without using it has no effect"]
pub fn build(self) -> Result<O, GObjectError> {
let type_ = self.type_.expect("Deferred Type must be set");
let mut properties = smallvec::SmallVec::<[_; 16]>::with_capacity(self.properties.len());
let klass = glib::Class::<O>::from_type(type_).unwrap();
for (name, value) in self.properties {
let pspec =
klass
.find_property(name)
.ok_or_else(|| GObjectError::PropertyNotFound {
type_,
property: name.into(),
})?;
match value {
ValueOrStr::Value(value) => properties.push((name, value)),
ValueOrStr::Str(value) => {
properties.push((name, value_from_property_str(pspec, value)?));
}
}
}
let object =
unsafe { glib::Object::with_mut_values(type_, &mut properties).unsafe_cast::<O>() };
Ok(object)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{prelude::*, Bin, Element, ElementFactory, Object};
#[test] #[test]
fn test_set_property_from_str() { fn test_set_property_from_str() {
crate::init().unwrap(); crate::init().unwrap();
let fakesink = crate::ElementFactory::make("fakesink").build().unwrap(); let fakesink = ElementFactory::make("fakesink").build().unwrap();
fakesink.set_property_from_str("state-error", "ready-to-paused"); fakesink.set_property_from_str("state-error", "ready-to-paused");
let v = fakesink.property_value("state-error"); let v = fakesink.property_value("state-error");
let (_klass, e) = glib::EnumValue::from_value(&v).unwrap(); let (_klass, e) = glib::EnumValue::from_value(&v).unwrap();
assert_eq!(e.nick(), "ready-to-paused"); assert_eq!(e.nick(), "ready-to-paused");
} }
#[test]
fn builder() {
crate::init().unwrap();
let msg_fwd = "message-forward";
let bin = Object::builder::<Bin>()
.name("test-bin")
.property("async-handling", true)
.property_from_str(msg_fwd, "True")
.build()
.unwrap();
assert_eq!(bin.name(), "test-bin");
assert!(bin.property::<bool>("async-handling"));
assert!(bin.property::<bool>("message-forward"));
}
#[test]
fn builder_err() {
crate::init().unwrap();
assert_eq!(
Object::builder::<Bin>()
.property("not-a-prop", true)
.build(),
Err(GObjectError::PropertyNotFound {
type_: Bin::static_type(),
property: idstr!("not-a-prop")
})
);
assert_eq!(
Object::builder::<Bin>()
.property_from_str("async-handling", "not-a-bool")
.build(),
Err(GObjectError::PropertyFromStr {
type_: Bin::static_type(),
property: idstr!("async-handling"),
value: idstr!("not-a-bool")
})
);
}
#[test]
fn builder_for() {
crate::init().unwrap();
let fakesink = ElementFactory::make("fakesink").build().unwrap();
let fakesink = Object::builder_for::<Element>(fakesink.type_())
.name("test-fakesink")
.property("can-activate-pull", true)
.property_from_str("state-error", "ready-to-paused")
.build()
.unwrap();
assert_eq!(fakesink.name(), "test-fakesink");
assert!(fakesink.property::<bool>("can-activate-pull"));
let v = fakesink.property_value("state-error");
let (_klass, e) = glib::EnumValue::from_value(&v).unwrap();
assert_eq!(e.nick(), "ready-to-paused");
}
} }

View file

@ -200,7 +200,7 @@ mod device_provider;
mod device_provider_factory; mod device_provider_factory;
mod enums; mod enums;
mod ghost_pad; mod ghost_pad;
mod gobject; pub mod gobject;
mod iterator; mod iterator;
mod object; mod object;
mod pad; mod pad;

View file

@ -35,8 +35,11 @@ impl Pipeline {
/// Creates a new builder-pattern struct instance to construct [`Pipeline`] objects. /// Creates a new builder-pattern struct instance to construct [`Pipeline`] objects.
/// ///
/// This method returns an instance of [`PipelineBuilder`] which can be used to create [`Pipeline`] objects. /// This method returns an instance of [`PipelineBuilder`] which can be used to create [`Pipeline`] objects.
pub fn builder() -> PipelineBuilder { pub fn builder<'a>() -> PipelineBuilder<'a> {
PipelineBuilder::new() assert_initialized_main_thread!();
PipelineBuilder {
builder: crate::Object::builder(),
}
} }
} }
@ -80,22 +83,21 @@ impl Default for Pipeline {
/// ///
/// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
#[must_use = "The builder must be built to be used"] #[must_use = "The builder must be built to be used"]
pub struct PipelineBuilder { pub struct PipelineBuilder<'a> {
builder: glib::object::ObjectBuilder<'static, Pipeline>, builder: crate::gobject::GObjectBuilder<'a, Pipeline>,
}
impl PipelineBuilder {
fn new() -> Self {
Self {
builder: glib::Object::builder(),
}
} }
impl<'a> PipelineBuilder<'a> {
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Build the [`Pipeline`]. /// Build the [`Pipeline`].
///
/// # Panics
///
/// This panics if the [`Pipeline`] doesn't have all the given properties or
/// property values of the wrong type are provided.
#[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"] #[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"]
pub fn build(self) -> Pipeline { pub fn build(self) -> Pipeline {
self.builder.build() self.builder.build().unwrap()
} }
pub fn auto_flush_bus(self, auto_flush_bus: bool) -> Self { pub fn auto_flush_bus(self, auto_flush_bus: bool) -> Self {
@ -188,25 +190,25 @@ impl PipelineBuilder {
} }
} }
pub fn name(self, name: impl Into<glib::GString>) -> Self { // rustdoc-stripper-ignore-next
/// Sets property `name` to the given value `value`.
///
/// Overrides any default or previously defined value for `name`.
#[inline]
pub fn property(self, name: &'a str, value: impl Into<glib::Value> + 'a) -> Self {
Self { Self {
builder: self.builder.property("name", name.into()), builder: self.builder.property(name, value),
} }
} }
pub fn name_if(self, name: impl Into<glib::GString>, predicate: bool) -> Self { // rustdoc-stripper-ignore-next
if predicate { /// Sets property `name` to the given string value `value`.
self.name(name) #[inline]
} else { pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self {
self Self {
builder: self.builder.property_from_str(name, value),
} }
} }
pub fn name_if_some(self, name: Option<impl Into<glib::GString>>) -> Self { impl_builder_gvalue_extra_setters!(property_and_name);
if let Some(name) = name {
self.name(name)
} else {
self
}
}
} }

View file

@ -1714,7 +1714,7 @@ macro_rules! impl_builder_gvalue_extra_setters (
} }
}; };
(property) => { (property_and_name) => {
// rustdoc-stripper-ignore-next // rustdoc-stripper-ignore-next
/// Sets property `name` to the given inner value if the `predicate` evaluates to `true`. /// Sets property `name` to the given inner value if the `predicate` evaluates to `true`.
/// ///
@ -1775,6 +1775,67 @@ macro_rules! impl_builder_gvalue_extra_setters (
self self
} }
} }
// rustdoc-stripper-ignore-next
/// Sets property `name` to the given string value `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_from_str_if(self, name: &'a str, value: &'a str, predicate: bool) -> Self {
if predicate {
self.property_from_str(name, value)
} else {
self
}
}
// rustdoc-stripper-ignore-next
/// Sets property `name` to the given string value `value` if it 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_from_str_if_some(self, name: &'a str, value: Option<&'a str>) -> Self {
if let Some(value) = value {
self.property_from_str(name, value)
} else {
self
}
}
// rustdoc-stripper-ignore-next
/// Sets the name property to the given `name`.
#[inline]
pub fn name(self, name: impl Into<$crate::glib::GString>) -> Self {
self.property("name", name.into())
}
// rustdoc-stripper-ignore-next
/// Sets the name property to the given `name` if the `predicate` evaluates to `true`.
///
/// This has no effect if the `predicate` evaluates to `false`,
/// i.e. default or previous name is kept.
#[inline]
pub fn name_if(self, name: impl Into<$crate::glib::GString>, predicate: bool) -> Self {
if predicate {
self.name(name)
} else {
self
}
}
// rustdoc-stripper-ignore-next
/// Sets the name property to the given `name` if it is `Some`.
///
/// This has no effect if the value is `None`, i.e. default or previous name is kept.
#[inline]
pub fn name_if_some(self, name: Option<impl Into<$crate::glib::GString>>) -> Self {
if let Some(name) = name {
self.name(name)
} else {
self
}
}
}; };
); );