-
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
Fix command targeting. #986
Conversation
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 sure is a nice simplification of the event handling logic as well, thanks!
The way events are filtered is still a bit irritating though.
druid/src/core.rs
Outdated
Target::Widget(id) if *id == self.id() => { | ||
is_handled = true; // Prevent it being passed down to children | ||
modified_event = Some(Event::Command(cmd.clone())); | ||
true | ||
} |
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 is this marked as handled...
druid/src/core.rs
Outdated
InternalEvent::RouteTimer(token, widget_id) => { | ||
let widget_id = *widget_id; | ||
if widget_id != child_ctx.widget_state.id { | ||
recurse = child_ctx.widget_state.children.may_contain(&widget_id); | ||
Event::Internal(InternalEvent::RouteTimer(*token, widget_id)) | ||
if *widget_id == self.id() { | ||
modified_event = Some(Event::Timer(*token)); | ||
true |
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.
... but not this?
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, I think I got this now:
Only commands targeted the current widget are marked as handled and will thus be ignored by nested WidgetPod
s while Timer events are filtered out regardless of being handled, as they are always targeted at a single widget.
This distinction is kind of irritating when reading the code, and should be documented if we stick to it.
Also I think it is weird that a widget, receiving a command targeted at it, gets that command already marked as handled
. I do understand that this is currently necessary because we loose the information about whether or not the command was targeted, once we convert it to a normal Event::Command
, but it still seems wrong to me.
The two possible solutions that come to my mind are either passing down both, the normal command and the internal in case it should propagate further, or adding the Target
to Event::Command
and always pass it down along the command.
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.
Initially I tried solving it by passing down the internal command, however that breaks a whole class of command usage. Stuff like ALL_TAKE_FOCUS_AFTER
in the test case. A command where a widget wants to decide whether to handle it after it has been passed down to children. This would no longer work if we pass an internal command that generates the regular command for each widget.
Adding Target
to Event::Command
is also something I considered, but I went with is_handled
because that is backwards compatible. I'm not commited to it though, and I'll write an extra comment with the pros and cons of doing either.
Target::Global => { | ||
for w in self.windows.iter_mut() { | ||
let event = Event::Command(cmd.clone()); | ||
let event = | ||
Event::Internal(InternalEvent::TargetedCommand(target, cmd.clone())); | ||
if w.event(&mut self.command_queue, event, &mut self.data, &self.env) { | ||
break; | ||
} |
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.
Not related to your PR but made me wonder:
Does it make sense to allow global commands to be 'handled'?
It seems wrong that global commands, which IMO are supposed to reach everywhere, can be cough by some widget, in a not necessarily deterministic way (as it depends on the window order).
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 touches upon the whole handled
concept. A similar problem applies to all the other events as well.
Setting handled
is never that deterministic, global or not. I could also see it working on a per-window basis for global commands. Either way it would probably be good to document that in the docs of Target
.
The problem with targeted commands is that:
The easiest to program solution, which is also backwards compatible compilation wise, is the one this PR has right now. When synthesizing the The problem with this approach is that it is a rather unique solution and primarily a result of wanting to keep backwards compatibility. The uniqueness means that it can be confusing - the propagation prevention doesn't look like any other event. @Finnerale already got confused by this and he has been working on commands. It's very likely others will be even more confused by it. An alternative solution would be to change Another positive effect of adding The downside of adding the When doing this PR originally I was on the fence about which way to go. I went with @cmyr you've used commands a bunch, do you think adding |
Okay sorry to leave this hanging. I think that this problem, in combination with some of the issues raised in the @Finnerale's work with selectors, suggests (to me) that command should include a target. I think this means that I don't think I totally understand the If we add a target to |
The main point of This is all very nice and it works right now. The point I was making is that this only works if this is a regular This is noteworthy because the propagation could also be solved by having an internal event go down which then Thus, global/window commands need to be Even if we add the target to
I'm not sure I understand your question. More generally on the topic of targeting. If an event is targeted it should be routed via internal events and then synthesized for the specific target widget, and further propagation down would be blocked. Some events like the timer already work like this and I think more should, including a command targeted at a specific widget, which is what this PR is all about. The major benefit of this precision targeting is a large reduction in boilerplate. Widgets don't have to ask the question whether an event was meant for them. If they receive an event - it's meant for them. Widgets also don't need logic to decide whether they should pass down an event to their children. They should just always do that and This means that widgets can check for event details and then do their reactive work. There's no need to have every widget decide about applicability to themselves or their children. The only downside I can think of is that a group of |
I think adding a target directly to This should give widgets complete control over when commands are propagated and automatically filters out widget-targeted commands. Adding the target to ctx.submit_command(SELECTOR.with(payload).to(Target::Widget(id))) |
Okay, have we made a decision here? What can I do to help? |
I'm going to close this for now, just to clean up a bit; happy to pick it up again if there's further interest. |
This PR fixes commands targeted at a specific widget being recursed to children. I also moved the command targeting logic more to one place (
WidgetPod::event
) as previously there was some target translation going on at the WinHandler level already, which meant that the test harness command sending was broken because the harness sends the commands untranslated.I also refactored the event recursing logic inWidgetPod::event
to be more like the newerWidgetPod::lifecycle
. The new approach is more flexible and also more performant because in a lot of cases there's no need to construct a new event.