-
Notifications
You must be signed in to change notification settings - Fork 567
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
Menus that use data #1625
Menus that use data #1625
Conversation
I think this is ready for a look. I think the new mac behavior is an improvement over what was there before, although I haven't been able to test it. Now, it keeps track of the most-recently-focused window with a menu and always treats that one as the global app menu. Previously, it would set the menu whenever the focus changed, but it would also change it on every |
@jneem very cool, will take a look at this in the next day or two! |
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.
This looks great! I've left some preliminary comments inline, although I haven't tried playing around with it. My initial impression is that this looks like a huge improvement, though, thanks for taking it on :)
needs_refresh: Option<MenuPredicate<T>>, | ||
item: MenuItem<T>, | ||
children: Vec<MenuEntry<T>>, | ||
// bloom? |
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.
This would require children to have some sort of identity, and for there to be some benefit in only visiting specific children. Do you think this would make sense?
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.
The MenuItem
s are supposed to have unique ids, so blooms would basically allow us to traverse directly to the right MenuItem
when getting a menu command from the platform.
I didn't do it yet because:
- the menu probably can't be big enough (or commands common enough) for traversing the menu tree to be a bottleneck, and
- it might actually be more expensive to construct bloom filters each time the menu is rebuilt (I have more to say about rebuilding, but I'll put that elsewhere later)
Thanks for your review! I think I addressed your comments except for the One remark about rebuilding: this implementation internally rebuilds the entire druid-shell menu from scratch each time anything changes, but I think that as long as the underlying platform API cooperates (I haven't checked anything except for GTK), we could do the updates entry-by-entry. It's a bit of a pain trying to coordinate the druid-shell changes across backends, so I haven't tried to do it here. |
Okay I'll take a closer look at the context menu situation and play around with it a bit; context menus are often presented in situations where the user doesn't have access to the root data (like in some widget deep in the tree) so I want to make sure there's a reasonable solution, there. |
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.
Okay so I updated runebender to use this and ran into a few problems; the major one is that this breaks the built-in commands for open files etc.
There are a few other thoughts inline; in any case this is exciting. :)
} | ||
.as_mut() | ||
.map(|m| m.event(queue, cmd_id, data, env)), | ||
}; |
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.
So this was a bit fragile, and these changes break some things. The basic problem is that this assumes that all menus attached to a window are only sending events that are targeted to that window, which may not be case in general and which has another particular consequence: we use a bunch of special built-in commands for things like opening files, and these need to be handled outside of Inner
; that's why the previous implementation returned a Command
.
From a higher-level, I think in general it's best to try, wherever possible, to reuse existing code paths. We have a mechanism for dispatching commands from the WinHandler
, and that code path is well-worn, and ensures that things like update
happen correctly. By taking this short-cut and calling event
directly, we're introducing a new codepath and that means we would have to take care, in the future, to ensure that any changes around how update happens or how events are routed also takes care of this special case. Because all events trigger updates of all windows, we need to make sure that all events originate outside of the Inner
struct.
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.
Thanks for the pointer! I ran into this myself today, but hadn't tracked down the reason yet...
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.
Ok, so having dug into this a bit more, I'm not sure I understand your comment. m.event
is the event method on MenuManager
, which is basically responsible for pushing commands on the command queue, and then those commands get dispatched using the usual AppState
command dispatching/updating mechanism. I did have a bug, however, where the target wasn't being set, which caused some commands to get dropped...
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.
oooh, so here the menu receives the cmd_id
, and then submits the command via the context? That sounds good. 👍
druid/src/menu/mod.rs
Outdated
|
||
impl<T: Data> ContextMenu<T> { | ||
/// Create a new [`ContextMenu`]. | ||
pub fn new( |
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.
So playing around with this in runebender, I wonder if we can simplify the context menu case?
My basic thinking: a context menu is a modal. It should never need to be updated, since it is dismissed if anything is clicked. Additionally, in a non-trivial application a context menu is going to be presented somewhere fairly deep in the widget hierarchy, where the root data is likely to be several lenses away.
For me, I think that having access to the root data (especially when it's basically untyped) in the context menu just isn't that helpful. I would much prefer if, in this case, I instantiate a single menu object and display it each time.
Does this make sense? It's a bit different from the API for other menus, but ultimately feels simpler than what we have now and potentially more flexible, since you can pass in whatever data you want when building your menu.
Dunno why, but github wouldn't let me reply to this directly:
Are you just talking about the menu construction here, or more generally about the various data-accessing callbacks in the menu? I definitely agree that we don't need the "builder" callback. I also think that things like
|
Those ideas make sense, I think? I'm not sure about the local data bit; it isn't totally obvious to me how that would work. What I could imagine, although it's a bit less ergonomic, is making the caller build the menu themselves, using the available data and env, and passing the I'm generally a fan ofconvenience methods. |
Ok, I think I've addressed the issues except for the question about access to the local data in context menus (which I haven't even looked into, and I'd prefer to punt on for now). What I did instead was make it so that all the relevant menu-building functions have both dynamic and static versions, so it's now much more convenient to construct a menu based on the current data that doesn't need access to future data. |
Will take another swing at this soon! |
I really like the API tweaks here, but I do have one lingering problem with the I'm not trying to be difficult, here; I legitimately spent 20+ minutes getting the context menu to work in runebender, and it ended up with me just cloning my local data and moving that into the closure, ignoring the closure arguments altogether, like: let data = data.clone();
let menu: ContextMenu<AppState> = ContextMenu::new(
move |_, _| crate::menus::make_context_menu(&data, mouse_pos),
m.window_pos,
); and this doesn't feel like a great idiom. Other than that, this looks good. The window menus are working now and I'm not seeing the problem with dropped commands, although when I'm slightly more alert I will go and review my last comment there to make sure I'm not totally losing it. Thanks again, this feels great! |
Sorry, with all the other API changes that were supposed to make context menus more convenient, the one you actually asked for slipped through the cracks... |
Great that looks like a solid simplification. Also I'm seeing a warning from CI about an 'unread field'? I'm going to take one more pass through the root command handling logic, but modulo that this looks good to go! |
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.
Looks good, thanks again for taking this on, it's a solid improvement.
} | ||
.as_mut() | ||
.map(|m| m.event(queue, cmd_id, data, env)), | ||
}; |
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.
oooh, so here the menu receives the cmd_id
, and then submits the command via the context? That sounds good. 👍
Menus are now updated semi-automatically from the app data.
Menus are now updated semi-automatically from the app data. The public menu API has been substantially changed. Fixes linebender#965
This is a draft of my attempt at #965