-
-
Notifications
You must be signed in to change notification settings - Fork 118
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
Add proc macro for binding methods to action maps #67
base: main
Are you sure you want to change the base?
Conversation
Before going any further: is it really worth it to create a whole new crate for this? Shouldn't we put it into glib-macros instead? Also: please add error message tests for your macro (like there are in |
fyi, the actions are part of gio not glib |
We can rename "glib-macros" into "gtk-rs-macros" or something along the line if you prefer? I'd simply prefer to avoid creating too many repositories. |
Don't we have a macro for |
Indeed we do |
Ah but that's not a proc macro so irrelevant here. We could put this into the |
Moving this to |
On Sun, 2021-06-06 at 03:30 -0700, Andrey Kutejko wrote:
Moving this to glib-macros will make a circular dependency. Doc-tests
depend on gio.
Well that decides it then :)
|
Is something blocking this PR at the moment? |
Someone who knows well about actions to review if the way how the macro exposes them makes sense, and someone to review if adding this macro causes us composibility problems in the future. |
@Hofer-Julian You may try this macro with my crate. |
I really like this solution. It has two weak points though, IMO. First, using functions means that capturing variables is impossible. This makes this solution a lot less attractive for using it in apps built on top of Relm4. My approach is more flexible and works without proc-macros but has some possible weak spots, as well. Maybe we can get the best out of both wolds 🤔 |
I never tried Relm (or Relm4), so cannot comment on this. But my macro is designed to mimic OOP methods (if you derive your widget) or "methods" (if your widget is just a composition or a newtype-wrapper). So, it captures
Isn't this a regular approach in Gtk-world? You place strings with action names (or action names and target values) here and there in a source code and Regarding type safety of actions. Once I gave a try to such approach and defined all actions of my app as It seems both of us agree on one thing: action has only one callback handler :) This simplifies its role to just a fancy alias to a callback handler. So, you may pass either a string or a type-safe value to widgets instead of an actual closure. |
@andy128k I'd like to see this updated with the latest bits from awesome-gtk (async stuff, lint fixes) and then I can review it, I've found this to be really useful |
/// | ||
/// A `stateful` annotation may be omited if `initial_state` or `change_state` is | ||
/// specified. | ||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe put a note in here about the caveats with async actions? Especially as it relates to returning state changes, it seems like most of the time a stateful action shouldn't be async. You can put a note similar to this one I put in the template callbacks macro: https://github.com/gtk-rs/gtk4-rs/blob/86c1548cc2a6fa05c4285802f0607b374f3bec0f/gtk4-macros/src/lib.rs#L122-L131
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? Such actions work as expected. Returning a value is just a fancy way to invoke set_state
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but it is possible with async actions to have two or more invocations of the action get spawned. If a later future resolves quicker than an earlier one, that would end up calling set_state
with a stale value. In a GTK widget that would be like pressing a button twice and seeing a widget update with the new value and the suddenly switching back to an old state.
To be totally free of races in stateful actions, it seems the app would have to do something like:
- Check the current state at the end of the async action, return
None
if it does not match the input state, or - If the order is important, generate a unique ID at the beginning of the action and push it on a stack (a heap pointer would do), take it off the stack at the end and return
None
if it was not at the top - Try to disable the action at the beginning of the async action, return
None
immediately if it's already disabled, then re-enable it at the end
pub fn actions( | ||
attrs: ActionImplAttributes, | ||
mut input: ItemImpl, | ||
) -> Result<TokenStream, TokenStream> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of returning a Result
and using to_compile_error
, you can catch the error, pass it to proc_macro_error::emit_error
and then generate an empty register method. I made the gtk4 macros do something like this and it works really well to reduce the error message if you have a nightly compiler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I think we may also want to accumulate more of these errors with combine
instead of returning early from this function?
A couple more things:
|
Do we want to require an |
There are multiple examples of actions with and without an attribute in tests. |
Let me modify the example to show what I mean. Would it help to be like this: #[gio::actions]
impl MyApplication {
#[action]
fn action1(&self) { /* this method gets added with add_action */ }
fn my_method(&self) { /* not an action, just an ordinary method */ }
} Then #[gio::actions]
#[gtk::template_callbacks]
impl MyWidget {
#[action]
fn action1(&self) { /* ... */ }
#[template_callback]
fn callback1(&self) { /* ... */ }
} What do you think? Or does it make sense to enforce a separate |
My opinion is that it is better to keep such blocks separated. Such macros may modify signatures of functions, generate new ones and step on each others feet any other way. |
Sounds good to me |
Co-authored-by: Jason Francis <93952137+jf2048@users.noreply.github.com>
@bilelmoussaoui As you worked on actions API, what do you think of this considering the existing helpers you added for making this easier? |
Current API is still very verbose and repetitive. I gave it a try and that's how it looks like just for two actions. window
.add_action_entries([
gio::ActionEntry::builder("find")
.activate(move |this: &Self::Type, _, _| {
let this = this.clone();
glib::MainContext::default().spawn_local(async move {
this.find().await;
});
})
.build(),
gio::ActionEntry::builder("copy")
.activate(move |this: &Self::Type, _, _| this.copy())
.build(),
])
.unwrap(); |
Continuation of gtk-rs/gtk3-rs#266
Closes #16