-
Notifications
You must be signed in to change notification settings - Fork 137
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 focus handling in multi-window applications #489
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -310,6 +310,9 @@ impl WindowHandle { | |
} | ||
} | ||
if was_focused != cx.app_state.focus { | ||
// What is this for? If you call ViewId.request_focus(), this | ||
// causes focus to be set to your view, then briefly set to | ||
// None here and then back. Why? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the app_state focus will taken from it's option before a pointer down even is propagated. Once event propagation is done, if another view has set itself to have focus because of the pointer down event the focus_changed function will notify the old was_focused that it's focus has been lost and the new app state focus that it has gained focus There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I found (I added dumping the stack to the focus method it calls if the value was I didn't check if events were fired for focus going to |
||
cx.app_state.focus_changed(was_focused, cx.app_state.focus); | ||
} | ||
|
||
|
@@ -701,11 +704,28 @@ impl WindowHandle { | |
CENTRAL_UPDATE_MESSAGES.with_borrow_mut(|central_msgs| { | ||
if !central_msgs.is_empty() { | ||
UPDATE_MESSAGES.with_borrow_mut(|msgs| { | ||
let central_msgs = std::mem::take(&mut *central_msgs); | ||
for (id, msg) in central_msgs { | ||
// We need to retain any messages which are for a view that either belongs | ||
// to a different window, or which does not yet have a root | ||
let removed_central_msgs = | ||
std::mem::replace(central_msgs, Vec::with_capacity(central_msgs.len())); | ||
for (id, msg) in removed_central_msgs { | ||
if let Some(root) = id.root() { | ||
let msgs = msgs.entry(root).or_default(); | ||
msgs.push(msg); | ||
} else { | ||
// Messages that are not for our root get put back - they may | ||
// belong to another window, or may be construction-time messages | ||
// for a View that does not yet have a window but will momentarily. | ||
// | ||
// Note that if there is a plethora of events for ids which were created | ||
// but never assigned to any view, they will probably pile up in here, | ||
// and if that becomes a real problem, we may want a garbage collection | ||
// mechanism, or give every message a max-touch-count and discard it | ||
// if it survives too many iterations through here. Unclear if there | ||
// are real-world app development patterns where that could actually be | ||
// an issue. Since any such mechanism would have some overhead, there | ||
// should be a proven need before building one. | ||
central_msgs.push((id, msg)); | ||
} | ||
} | ||
}); | ||
|
@@ -716,11 +736,17 @@ impl WindowHandle { | |
if !central_msgs.borrow().is_empty() { | ||
DEFERRED_UPDATE_MESSAGES.with(|msgs| { | ||
let mut msgs = msgs.borrow_mut(); | ||
let central_msgs = std::mem::take(&mut *central_msgs.borrow_mut()); | ||
for (id, msg) in central_msgs { | ||
let removed_central_msgs = std::mem::replace( | ||
&mut *central_msgs.borrow_mut(), | ||
Vec::with_capacity(msgs.len()), | ||
); | ||
let unprocessed = &mut *central_msgs.borrow_mut(); | ||
for (id, msg) in removed_central_msgs { | ||
if let Some(root) = id.root() { | ||
let msgs = msgs.entry(root).or_default(); | ||
msgs.push((id, msg)); | ||
} else { | ||
unprocessed.push((id, msg)); | ||
} | ||
} | ||
}); | ||
|
@@ -748,11 +774,40 @@ impl WindowHandle { | |
if cx.app_state.focus != Some(id) { | ||
let old = cx.app_state.focus; | ||
cx.app_state.focus = Some(id); | ||
|
||
// Fix: Focus events were never dispatched to views' own | ||
// event_before_children / event_after_children. | ||
// | ||
// This is a bit messy; could it be done in ViewId.apply_event() | ||
// instead? Are there other cases of dropped events? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other events are from winit, so they would be dispatched by the |
||
let mut ec = EventCx { | ||
app_state: cx.app_state, | ||
}; | ||
|
||
if let Some(old) = old.as_ref() { | ||
ec.unconditional_view_event(*old, Event::FocusLost, true); | ||
} | ||
ec.unconditional_view_event(id, Event::FocusGained, true); | ||
|
||
cx = UpdateCx { | ||
app_state: ec.app_state, | ||
}; | ||
|
||
cx.app_state.focus_changed(old, cx.app_state.focus); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
E.g. when the focus got changed due to pointer click. |
||
} | ||
} | ||
UpdateMessage::ClearFocus(id) => { | ||
let old = cx.app_state.focus; | ||
cx.app_state.clear_focus(); | ||
if let Some(old) = old { | ||
let mut ec = EventCx { | ||
app_state: cx.app_state, | ||
}; | ||
ec.unconditional_view_event(old, Event::FocusLost, true); | ||
cx = UpdateCx { | ||
app_state: ec.app_state, | ||
}; | ||
} | ||
cx.app_state.focus_changed(Some(id), None); | ||
} | ||
UpdateMessage::Active(id) => { | ||
|
This comment was marked as resolved.
Sorry, something went wrong.
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.
Happy to leave it as-is too. Though I think if you have enough view ids to exhaust the stack, you might have some UI usability problems far worse than hitting that limit :-)