Skip to content
This repository has been archived by the owner on Mar 4, 2024. It is now read-only.

Add automatic signal connection in combination with gtk::Builder #128

Open
sdroege opened this issue May 3, 2020 · 7 comments
Open

Add automatic signal connection in combination with gtk::Builder #128

sdroege opened this issue May 3, 2020 · 7 comments
Labels
enhancement New feature or request gtk

Comments

@sdroege
Copy link
Member

sdroege commented May 3, 2020

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
Copy link
Member Author

sdroege commented Jun 28, 2020

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

@piegamesde
Copy link
Contributor

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
Copy link
Contributor

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.

@ids1024
Copy link
Contributor

ids1024 commented Feb 4, 2021

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: jdonszelmann/label#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 #269.

@jdonszelmann
Copy link

jdonszelmann commented Feb 5, 2021

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

@ids1024
Copy link
Contributor

ids1024 commented Feb 5, 2021

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.

@jdonszelmann
Copy link

jdonszelmann commented Feb 5, 2021

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request gtk
Projects
None yet
Development

No branches or pull requests

5 participants