-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
First-class technique for splitting input streams or absorbing input events #3570
Comments
Here are a few of the Use cases I can see involved, without too much of an opinion on how this could be solved: There are a few cases which complicate how events may want to be handled:
Possible, high level solutions: Event consumption registration:
It is probably most flexible that the
with
|
I think a plugin should not remove info from the built-in When having only one plugin that want to filter events, I originally thought that it must create a new event type and resend original Bevy events, mapping them to its own type, and filtering them to remove the one it already dealt with but that doesn't scale if more than one plugin want to capture events. For that case I'm thinking now it could be interesting to be able to attach tags to events, and then being able to filter base on those tags so that you could asks for |
Yes I agree. I hope that this serves to illustrate the point that there are currently no relevant abstractions that relieve the need for such egregious hacks. |
Here' s my suggestion:
I use a more sophisticated system that uses ray casts to assign focus, I've approximated this here with Without such a system, gestures would be very hard to implement. Even corner cases would cause trouble, like clicking a button, moving off the button and back (or not), then releasing. So maybe it makes sense to implement a higher level input processing system as I have done, as a separate plugin. Also, multi-touch and mouse input are quite different, so each might use a different plugin. |
Okay, interesting. So you'd have something like a: struct CurrentlyFocused(Option<Box<dyn Focusable>>); This would be extremely useful for bevyengine/rfcs#41 IMO you have to use a trait object there: enums are the natural fit but they're not composable. Do we ever want to have multiple objects / areas focused at once? No, not really. And if users need this, they can do make their own weird struct to represent hybrid states. |
This is an interesting thought. There have been a number of issues and discussions around events that an API like this could address. I know I have been working around a situation similar to #1431 that this would work perfectly for in my own project. Seems to me this could also handle the same situations as things like #1626 in a very "bevy way". |
The input to my app is multi-touch, so it can assign multiple contact points to multiple objects. For single point input, it might make sense to store all layers to form a hierarchy. For example, clicking a text box might also highlight the box enclosing (in a layer behind) the text box. So you could have: type Layer = usize;
struct CurrentlyFocused(Vec<(Layer, Box<dyn Focusable>)>); and keep it sorted by struct CurrentlyFocused(Vec<(Layer, Entity)>); Claiming would clear entries with the same or higher layer and a lower tick, replacing that part of the hierarchy. No action should be taken based on |
I find @HackerFoo 's suggestion very interesting. I've actually came up with a similar system independently in ui-navigation (although didn't implement it yet). What you call There is no real concept of ownership, basically the locking mechanism is just a way to toggle off input for the ui-nav system. Without such an escape hatch, the user would need to account for my plugin in their own input management systems, which is not ideal. For ui-nav, focus claim contention is irrelevant, because it imposes a focus hierarchy and is the sole source of truth for knowing what is focused. This also means the Beyond that, there no motion vector clipping or coordinate system adjustments. |
I run into this using Egui and mouse events in my game. Is there any solution now? Thanks. Best regards Sabi |
Thank you for creating this PR and the discussions above, I am eager to see it added to Bevy in the future; dealing with mouse clicks (needing to ray cast into the game world) going through UI has been a big pain point for me thus far. |
IMO it's more of a UI thing. In most case having #[derive(Resource, Default)]
pub struct UiHandling {
pub is_pointer_over_ui: bool,
}
#[derive(Component)]
pub struct NoPointerCapture;
fn check_ui_interaction(
mut ui_handling: ResMut<UiHandling>,
interaction_query: Query<
&Interaction,
(With<Node>, Changed<Interaction>, Without<NoPointerCapture>),
>,
) {
ui_handling.is_pointer_over_ui = interaction_query
.iter()
.any(|i| matches!(i, Interaction::Clicked | Interaction::Hovered));
} Or in further, let knowing if a event was read (read events when ui clicked/typed). For example, record event on reading and clear on update, this should be easier than synchronizing EventReaders. |
Correct me if I'm wrong, but this seemingly does nothing to alleviate the fact that interactions aren't absorbed by the top-level element (and there's no native way of achieving it cleanly) which is what the discussion centers on. With your snippet, multiple elements on top of each other could all return true for |
Well, with this snippet you're able to know if an area is covered by UI. You still have to manually ignore this operation in lower-levels' logic if you want. As for invisible nodes it also works, their |
|
What I want to see is an ecosystem in which frameworks like Leafwing Input Manager (LWIM) and bevy_eventlistener (BEL) can coexist. We are already part of the way there in that both of these frameworks support various kinds of contextual routing:
This means that we already have a viable solution for the "modal" operation where either one or the other (LWIM or BEL) is in complete control - so for example, in the game settings mode, BEL could handle all events, whereas in the main game HUD LWIM could take over. This can be done by (a) ensuring that LWIM is disabled in the settings game state, and (b) ensuring that no entities in the main HUD have BEL input handlers registered. What we're lacking, though, is a solution for the case where both input managers are active simultaneously. An example use case is a game editor with a live preview window: we would want arrow keys to be able to, say, rotate the camera, unless there is a text input field that currently has focus, in which case the arrow keys would instead be used to move the text cursor. As mentioned in the previous post, the way this sort of thing is typically done on the web is to install a top-level event handler on the document root. Widgets such as buttons, checkboxes, and text input fields will intercept events bubbling upward by calling Things are a bit trickier in Bevy because there is no global document root: there can be an arbitrary number of separate UI graphs. One approach here is to define a "catch-all" listener at the root of each UI hierarchy. This, however, requires the user to remember to install the catch-all handler for each UI graph. This can be especially troublesome in the case of modal dialogs and popup menus: for reasons of layout, we want these elements to be globally positioned relative to the window, so they need to be unparented. (A better solution would be to support "Fixed" positioning as mentioned in #9564). If the dialog or menu widget is being provided by a third-party crate, then the crate may not know about the need to forward events that bubble up to the top, and might not provide a way to install a catch-all handler at that level. Alternatively, we could modify BEL to automatically forward unhandled events to LWIM. Unfortunately, this creates an incestuous coupling between two crates that ought to be otherwise independent. This is where my ideas get a bit nebulous, but I've been envisioning something like an "Input Graph" analogous to the "Animation Graph". For example, one can envision a graph that looks like this: graph TD;
GamePad1-->LWIM;
GamePad2-->LWIM;
PointingDevice-->BEL;
Keyboard-->BEL;
BEL-->LWIM;
In this graph we have "source" or "producer" nodes which represent various kinds of hardware input devices, "intermediate" nodes which have both inputs and outputs, and "consumer" nodes which only consume events. These nodes would be wired together at app initialization time, although the wiring could be conditional based on the current game state. In this example, BEL would only forward to the next stage events which had not had One requirement for a node graph like this is that all of the events being passed between the nodes would have to have a standardized type. This includes not only the event payload, but any transitional information needed to calculate |
One method that would make this easy to work with would be to extend |
What problem does this solve or what need does it fill?
Typically, when users click on a UI element (including in game units), we don't want to also handle that click in the rest of the world.
The same pattern is observed when typing into a chat box, where the keyboard inputs are stolen.
Right now, the default behavior is to pass all events through.
What solution would you like?
A few solutions immediately come to mind:
split
method toResMut<Events<T>>
, which returns two iterators of events based on whether the supplied predicate is true. This doesn't make the chaos any better, but does improve ergonomics.What alternative(s) have you considered?
Consume events in one-off systems using
ResMut<Events<Input<T>>>
.This was the approach taken in vladbat00/bevy_egui#50, specifically vladbat00/bevy_egui@53c1773#diff-420f4e08a25299e0e1fb09f4757e1d5d027a2278f37030c09ab9c06786cfa52eR283.
However this is not a particularly principle or discoverable approach, and risks serious chaos as input events are silently eaten according to decentralized rules based on system ordering.
Additional context
Initially discussed on Discord. Led to #3569. Related reddit post with proposed workarounds.
The text was updated successfully, but these errors were encountered: