diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index bbdebfd161..d514d76178 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -270,12 +270,7 @@ impl<'a> EventCtx<'a> { /// /// [`is_focused`]: struct.EventCtx.html#method.is_focused pub fn has_focus(&self) -> bool { - // The bloom filter we're checking can return false positives. - let is_child = self - .focus_widget - .map(|id| self.base_state.children.may_contain(&id)) - .unwrap_or(false); - is_child || self.focus_widget == Some(self.widget_id()) + self.base_state.has_focus } /// Request keyboard focus. @@ -613,12 +608,7 @@ impl<'a, 'b: 'a> PaintCtx<'a, 'b> { /// [`is_focused`]: #method.is_focused /// [`EventCtx::is_focused`]: struct.EventCtx.html#method.is_focused pub fn has_focus(&self) -> bool { - // The bloom filter we're checking can return false positives. - let is_child = self - .focus_widget - .map(|id| self.base_state.children.may_contain(&id)) - .unwrap_or(false); - is_child || self.focus_widget == Some(self.widget_id()) + self.base_state.has_focus } /// Returns the currently visible [`Region`]. diff --git a/druid/src/core.rs b/druid/src/core.rs index c23402dec6..86364fc02a 100644 --- a/druid/src/core.rs +++ b/druid/src/core.rs @@ -86,6 +86,10 @@ pub(crate) struct BaseState { /// Any descendant is active. has_active: bool, + /// In the focused path, starting from window and ending at the focused widget. + /// Descendants of the focused widget are not in the focused path. + pub(crate) has_focus: bool, + /// Any descendant has requested an animation frame. pub(crate) request_anim: bool, @@ -514,20 +518,28 @@ impl> WidgetPod { }; if let Some(change) = this_changed { - let event = LifeCycle::FocusChanged(change); - self.inner.lifecycle(ctx, &event, data, env); + // Only send FocusChanged in case there's actual change + if old != new { + self.state.has_focus = change; + let event = LifeCycle::FocusChanged(change); + self.inner.lifecycle(ctx, &event, data, env); + } false } else { + self.state.has_focus = false; // Recurse when the target widgets could be our descendants. // The bloom filter we're checking can return false positives. - old.map(|id| self.state.children.may_contain(&id)) - .or_else(|| new.map(|id| self.state.children.may_contain(&id))) - .unwrap_or(false) + match (old, new) { + (Some(old), _) if self.state.children.may_contain(old) => true, + (_, Some(new)) if self.state.children.may_contain(new) => true, + _ => false, + } } } LifeCycle::FocusChanged(_) => { - self.state.request_focus = None; - true + // We are a descendant of a widget that has/had focus. + // Descendants don't inherit focus, so don't recurse. + false } #[cfg(test)] LifeCycle::DebugRequestState { widget, state_cell } => { @@ -624,6 +636,7 @@ impl BaseState { needs_layout: false, is_active: false, has_active: false, + has_focus: false, request_anim: false, request_timer: false, request_focus: None, @@ -640,6 +653,7 @@ impl BaseState { self.request_anim |= child_state.request_anim; self.request_timer |= child_state.request_timer; self.has_active |= child_state.has_active; + self.has_focus |= child_state.has_focus; self.children_changed |= child_state.children_changed; self.request_focus = self.request_focus.or(child_state.request_focus); } diff --git a/druid/src/window.rs b/druid/src/window.rs index 8be999066b..826e4f8abc 100644 --- a/druid/src/window.rs +++ b/druid/src/window.rs @@ -328,22 +328,37 @@ impl Window { match focus { FocusChange::Resign => None, FocusChange::Focus(id) => Some(id), - FocusChange::Next => self - .focus - .and_then(|id| self.focus_chain().iter().position(|i| i == &id)) - .map(|idx| { - let next_idx = (idx + 1) % self.focus_chain().len(); - self.focus_chain()[next_idx] - }), - FocusChange::Previous => self - .focus - .and_then(|id| self.focus_chain().iter().position(|i| i == &id)) + FocusChange::Next => self.widget_from_focus_chain(true), + FocusChange::Previous => self.widget_from_focus_chain(false), + } + } + + fn widget_from_focus_chain(&self, forward: bool) -> Option { + self.focus.and_then(|focus| { + self.focus_chain() + .iter() + // Find where the focused widget is in the focus chain + .position(|id| id == &focus) .map(|idx| { + // Return the id that's next to it in the focus chain let len = self.focus_chain().len(); - let prev_idx = (idx + len - 1) % len; - self.focus_chain()[prev_idx] - }), - } + let new_idx = if forward { + (idx + 1) % len + } else { + (idx + len - 1) % len + }; + self.focus_chain()[new_idx] + }) + .or_else(|| { + // If the currently focused widget isn't in the focus chain, + // then we'll just return the first/last entry of the chain, if any. + if forward { + self.focus_chain().first().copied() + } else { + self.focus_chain().last().copied() + } + }) + }) } }