gtk-rs-core icon indicating copy to clipboard operation
gtk-rs-core copied to clipboard

Add derive macro for structs representing GObject properties

Open sdroege opened this issue 4 years ago • 2 comments

Kind of related to https://github.com/gtk-rs/glib/issues/637 and I would do them together in a few days.

I'll write out the whole plan here once I'm at a computer again, but the basic idea would be a derive macro and corresponding infrastructure that

  • registers a GObject property per struct field, incl default, min, max values etc
  • provides a set/get property function that directly does the translation
  • provides a notify function for notifying changes

The struct itself would then be stored inside the instance data in a refcell or mutex.

This should allow to bring down the boilerplate for GObject subclasses with properties considerably.

sdroege avatar Jul 23 '20 13:07 sdroege

I've had too many ideas about this, so I guess I might as well comment some here. Don't know what ideas anyone else has for how this should work.

Vala is probably a good model for glib property syntax.

The struct itself would then be stored inside the instance data in a refcell or mutex.

It probably doesn't matter that much, but Cell also makes sense for small Copy types (like a lot of properties).

provides a set/get property function that directly does the translation

This complicates things somewhat since these should be methods of the wrapper type. It looks like impl Self::Type won't work.

Anyway, something like this could be good:

#[derive(glib::ObjectProperties)]
struct Something {
    #[prop]
    foo: RefCell<String>,
   // I guess unlike Vala, something like this isn't really sensible,
   // since it will add an unused member to the struct?
   // I guess it could use `PhantomData` or something magically removed by the macro.
   #[prop(ro, get="self.get_bar")]
   bar: Cell<i32>,
}

The default value would be as defined by Default::default unless something else is specified. min and max could likewise default to the minimum and maximum of the type.

Having to manually implement ObjectImpl::{properties, set_property, get_property} to use this could be somewhat verbose. Maybe an attribute macro for the ObjectImpl block to add those?

Not sure it's a good idea, but I thought of using an enum, and I recall someone else mentioning that on IRC. So here's something like that:

#[glib::object_properties]
enum FooProperty {
    #[prop(rw)]
    Bar(i32)
}

impl ObjectImpl for ... {
    type Property = FooProperty;

    fn set_property(self, obj: &Self::Type, prop: FooProperty) {
        match prop {
           FooProperty::Bar(val) => ...
       }
    }

    fn get_propety(self, obj: &Self::Type, prop: &mut FooProperty {
        match prop {
            FooProperty::Bar(ref mut val) => ...
        }
    }
}

ids1024 avatar Apr 02 '21 16:04 ids1024

the glib::Value related traits are refactored now so that you can always get back what you're asking for. In addition there's now a ValueType trait that is implemented by all 'static types that can be used in glib::Values (i.e. everything you could use for storing a GObject property value!).

Another thing that would be missing for automatically generating code for property things now is something to get from a type to its corresponding glib::ParamSpec and creating an instance of it in a generic way.

One proposal in a PR of mine was

/// A type for which a `ParamSpec` exists and that can be used for object properties.
pub trait UsableAsParam: ValueType {
    /// Create a new default `ParamSpec` for this type.
    ///
    /// For types that allow specifiying a default value or valid range, these will be initialized
    /// with the equivalent of `None` or `0` and the whole valid range.
    fn param_spec(name: &str, nick: &str, blurb: &str, flags: ParamFlags) -> ParamSpec;
}

/// A type for which a `ParamSpec` exists that allows specifying a default value.
pub trait UsableAsParamWithDefault: UsableAsParam {
    /// Create a new `ParamSpec` for this type with a default value.
    ///
    /// For types that allow specifying a valid range, these will be initialized with the whole
    /// valid range.
    fn param_spec_with_default(
        name: &str,
        nick: &str,
        blurb: &str,
        // FIXME: Should probably be `Self`
        default: &Self,
        flags: ParamFlags,
    ) -> ParamSpec;
}

/// A type for which a `ParamSpec` exists that allows specifying a default value and valid range.
pub trait UsableAsParamWithMinMax: UsableAsParamWithDefault {
    /// Create a new `ParamSpec` for this type.
    fn param_spec_with_min_max(
        name: &str,
        nick: &str,
        blurb: &str,
        min: &Self,
        max: &Self,
        default: &Self,
        flags: ParamFlags,
    ) -> ParamSpec;
}

This however does not cover a couple of special cases: g_param_spec_gtype(), g_param_spec_value_array(), g_param_spec_variant() which all have a special parameter.

Also it doesn't fit with g_param_spec_flags(), g_param_spec_enum() but that's less of a problem as the trait would be directly implemented on the concrete enum/flags type anyway.

sdroege avatar Apr 28 '21 14:04 sdroege

I suppose this can be closed now?

bilelmoussaoui avatar Feb 17 '23 10:02 bilelmoussaoui