gtk3-rs icon indicating copy to clipboard operation
gtk3-rs copied to clipboard

Add automatic signal connection in combination with gtk::Builder

Open sdroege opened this issue 4 years ago • 7 comments

The idea here would be to annotate the signal handler functions with a procedural macro that would export the signature of the function so it can be checked at runtime.

E.g.

#[gtk::signal_handler("my_button_clicked")]
fn button_clicked_handler(&self, button: &gtk::Button) { ... }

This would then expand to something like

#[no_mangle]
pub fn gtk_rs_priv_my_button_clicked(&self, args: &[glib::Value]) -> Option<glib::Value> {
    let button = args[0].get::<gtk::Button>().expect("Wrong type").expect("Unexpected None");
    self.button_clicked_handler(&button);
}

fn button_clicked_handler(&self, button: &gtk::Button) { ... }

The nullability of the arguments would have to be detected/handled automatically based on the types.

Now for connecting the whole thing we would do a new function on BuilderExt that calls builder.connect_signals(), assumes/requires that &self is cloneable, and then via dlsym (etc) checks for each name if gtk_rs_priv_NAME does exist, e.g. gtk_rs_priv_my_button_clicked.

Instead of only supporting a &self first parameter we should also allow arbitrary other types that are cloneable, which shouldn't be too hard in addition either.

sdroege avatar May 03 '20 14:05 sdroege

Could maybe make use of https://crates.io/crates/label for this

sdroege avatar Jun 28 '20 16:06 sdroege

The basic problem is that we have a function with a fixed signature of dynamic types that needs to downcast to static types and call the special function. My current best solution to this is by implementing a common trait for that function:

pub trait SignalCallback {
    fn call(&self, args: &[glib::Value]) -> Option<glib::Value>;
}

impl SignalCallback for Fn<&gtk::Button> {
    fn call(&self, args: &[glib::Value]) -> Option<glib::Value> {
        let button = args[0].get::<gtk::Button>().expect("Wrong type").expect("Unexpected None");
        self(&button);
        None
    }
}

…

let callback: impl SignalCallback = button_clicked_handler as SignalCallback;

Obviously this has a few drawbacks and I left out a few important things like the actual self parameter of the callback. You can do this either by adding this: Any or by making the trait itself generic: SimpleCallback<Button>.

piegamesde avatar Sep 21 '20 13:09 piegamesde

Some prior art: https://idanarye.github.io/woab/woab/derive.BuilderSignal.html

It's a derive macro that for an enum with one variant per signal type.

piegamesde avatar Jan 30 '21 13:01 piegamesde

Looking at pygobject, this seems to be done with gtk_widget_class_set_connect_func on GTK3 and GtkBuilderScope with GTK4.

Could maybe make use of https://crates.io/crates/label for this

That uses the ctor crate. Not sure if that limits platform compatibility in an important way, but the approach seems potentially problematic: https://github.com/jonay2000/label/issues/28

It seems better to instead use a proc macro on the impl block, with something like this:

#[gtk::composite_template]
impl Foo {
    #[signal]
    foo_bar(&self, widget: &Self::Class) {}
}

Though if it's on impl Foo instead of impl WidgetImpl for Foo, that also leaves the issue of how to automatically call the function registering the signals, other than explicitly like in https://github.com/gtk-rs/gtk-rs/pull/269.

ids1024 avatar Feb 04 '21 18:02 ids1024

Hi, I'm the maker of that crate :). The reason it uses ctor is because otherwise you can't really register some item in code against some other part of the code. How would using a proc macro on the impl block change this? Luckily ctor is quite cross-platform image

What's unfortunate about it is that explicit support for every platform has to be added. Most platforms just do it the same way linux does, but that needs to be reassessed for each platform. Luckily this does seem quite easy image

jdonszelmann avatar Feb 05 '21 21:02 jdonszelmann

How would using a proc macro on the impl block change this?

My idea is that with the example code I gave, every function being registered would be within the same block, processed through one invocation of a gtk::composite_template macro. So there would be no need to share global state between different invocations of a macro, and it can be handled at once.

ids1024 avatar Feb 05 '21 21:02 ids1024

ah, you just make some local state for every implementation. And you annotate the functions you want. Though It'd be awesome to see my library used in some larger project, that's I think an objectively better approach.

jdonszelmann avatar Feb 05 '21 22:02 jdonszelmann