Skip to content

Commit e637e53

Browse files
authored
Fix for RouteFocusChanged and focusable descendants (#925)
1 parent 3332b17 commit e637e53

File tree

4 files changed

+116
-23
lines changed

4 files changed

+116
-23
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ While some features like the clipboard, menus or file dialogs are not yet availa
8080
- X11: Support `Application::quit`. ([#900] by [@xStrom])
8181
- GTK: Support file filters in open/save dialogs. ([#903] by [@jneem])
8282
- X11: Support key and mouse button state. ([#920] by [@jneem])
83+
- Routing `LifeCycle::FocusChanged` to descendant widgets. ([#925] by [@yrns])
8384

8485
### Visual
8586

@@ -167,6 +168,7 @@ While some features like the clipboard, menus or file dialogs are not yet availa
167168
[#917]: https://github.com/xi-editor/druid/pull/917
168169
[#920]: https://github.com/xi-editor/druid/pull/920
169170
[#924]: https://github.com/xi-editor/druid/pull/924
171+
[#925]: https://github.com/xi-editor/druid/pull/925
170172

171173
## [0.5.0] - 2020-04-01
172174

@@ -196,6 +198,7 @@ Last release without a changelog :(
196198
[@sjoshid]: https://github.com/sjoshid
197199
[@mastfissh]: https://github.com/mastfissh
198200
[@Zarenor]: https://github.com/Zarenor
201+
[@yrns]: https://github.com/yrns
199202

200203
[Unreleased]: https://github.com/xi-editor/druid/compare/v0.5.0...master
201204
[0.5.0]: https://github.com/xi-editor/druid/compare/v0.4.0...v0.5.0

druid/src/core.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -699,8 +699,8 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
699699

700700
pub fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
701701
// in the case of an internal routing event, if we are at our target
702-
// we may replace the routing event with the actual event
703-
let mut substitute_event = None;
702+
// we may send an extra event after the actual event
703+
let mut extra_event = None;
704704

705705
let recurse = match event {
706706
LifeCycle::Internal(internal) => match internal {
@@ -735,20 +735,18 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
735735
// Only send FocusChanged in case there's actual change
736736
if old != new {
737737
self.state.has_focus = change;
738-
substitute_event = Some(LifeCycle::FocusChanged(change));
739-
true
740-
} else {
741-
false
738+
extra_event = Some(LifeCycle::FocusChanged(change));
742739
}
743740
} else {
744741
self.state.has_focus = false;
745-
// Recurse when the target widgets could be our descendants.
746-
// The bloom filter we're checking can return false positives.
747-
match (old, new) {
748-
(Some(old), _) if self.state.children.may_contain(old) => true,
749-
(_, Some(new)) if self.state.children.may_contain(new) => true,
750-
_ => false,
751-
}
742+
}
743+
744+
// Recurse when the target widgets could be our descendants.
745+
// The bloom filter we're checking can return false positives.
746+
match (old, new) {
747+
(Some(old), _) if self.state.children.may_contain(old) => true,
748+
(_, Some(new)) if self.state.children.may_contain(new) => true,
749+
_ => false,
752750
}
753751
}
754752
#[cfg(test)]
@@ -790,15 +788,17 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
790788
}
791789
};
792790

793-
// use the substitute event, if one exists
794-
let event = substitute_event.as_ref().unwrap_or(event);
791+
let mut child_ctx = LifeCycleCtx {
792+
command_queue: ctx.command_queue,
793+
base_state: &mut self.state,
794+
window_id: ctx.window_id,
795+
};
795796

796797
if recurse {
797-
let mut child_ctx = LifeCycleCtx {
798-
command_queue: ctx.command_queue,
799-
base_state: &mut self.state,
800-
window_id: ctx.window_id,
801-
};
798+
self.inner.lifecycle(&mut child_ctx, event, data, env);
799+
}
800+
801+
if let Some(event) = extra_event.as_ref() {
802802
self.inner.lifecycle(&mut child_ctx, event, data, env);
803803
}
804804

druid/src/tests/helpers.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ macro_rules! assert_matches {
3333
}
3434
}
3535

36-
pub type EventFn<S, T> = dyn FnMut(&mut S, &mut EventCtx, &Event, &T, &Env);
36+
pub type EventFn<S, T> = dyn FnMut(&mut S, &mut EventCtx, &Event, &mut T, &Env);
3737
pub type LifeCycleFn<S, T> = dyn FnMut(&mut S, &mut LifeCycleCtx, &LifeCycle, &T, &Env);
3838
pub type UpdateFn<S, T> = dyn FnMut(&mut S, &mut UpdateCtx, &T, &T, &Env);
3939
pub type LayoutFn<S, T> = dyn FnMut(&mut S, &mut LayoutCtx, &BoxConstraints, &T, &Env) -> Size;
@@ -83,7 +83,7 @@ pub struct Recording(Rc<RefCell<VecDeque<Record>>>);
8383

8484
/// A recording of a method call on a widget.
8585
///
86-
/// Each member of the enum coorresponds to one of the methods on `Widget`.
86+
/// Each member of the enum corresponds to one of the methods on `Widget`.
8787
#[derive(Debug, Clone)]
8888
pub enum Record {
8989
/// An `Event`.
@@ -126,7 +126,7 @@ impl<S, T> ModularWidget<S, T> {
126126

127127
pub fn event_fn(
128128
mut self,
129-
f: impl FnMut(&mut S, &mut EventCtx, &Event, &T, &Env) + 'static,
129+
f: impl FnMut(&mut S, &mut EventCtx, &Event, &mut T, &Env) + 'static,
130130
) -> Self {
131131
self.event = Some(Box::new(f));
132132
self
@@ -262,6 +262,15 @@ impl Recording {
262262
self.0.borrow_mut().pop_front().unwrap_or(Record::None)
263263
}
264264

265+
/// Returns an iterator of events drained from the recording.
266+
pub fn drain(&self) -> impl Iterator<Item = Record> {
267+
self.0
268+
.borrow_mut()
269+
.drain(..)
270+
.collect::<Vec<_>>()
271+
.into_iter()
272+
}
273+
265274
fn push(&self, event: Record) {
266275
self.0.borrow_mut().push_back(event)
267276
}

druid/src/tests/mod.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,87 @@ fn take_focus() {
202202
})
203203
}
204204

205+
#[test]
206+
fn focus_changed() {
207+
const TAKE_FOCUS: Selector = Selector::new("druid-tests.take-focus");
208+
209+
fn make_focus_container(children: Vec<WidgetPod<(), Box<dyn Widget<()>>>>) -> impl Widget<()> {
210+
ModularWidget::new(children)
211+
.event_fn(|children, ctx, event, data, env| {
212+
if let Event::Command(cmd) = event {
213+
if cmd.selector == TAKE_FOCUS {
214+
ctx.request_focus();
215+
// Stop propagating this command so children
216+
// aren't requesting focus too.
217+
ctx.set_handled();
218+
}
219+
}
220+
children
221+
.iter_mut()
222+
.for_each(|a| a.event(ctx, event, data, env));
223+
})
224+
.lifecycle_fn(|children, ctx, event, data, env| {
225+
children
226+
.iter_mut()
227+
.for_each(|a| a.lifecycle(ctx, event, data, env));
228+
})
229+
}
230+
231+
let a_rec = Recording::default();
232+
let b_rec = Recording::default();
233+
let c_rec = Recording::default();
234+
235+
let (id_a, id_b, id_c) = widget_id3();
236+
237+
// a contains b which contains c
238+
let c = make_focus_container(vec![]).record(&c_rec).with_id(id_c);
239+
let b = make_focus_container(vec![WidgetPod::new(c).boxed()])
240+
.record(&b_rec)
241+
.with_id(id_b);
242+
let a = make_focus_container(vec![WidgetPod::new(b).boxed()])
243+
.record(&a_rec)
244+
.with_id(id_a);
245+
246+
let f = |a| match a {
247+
Record::L(LifeCycle::FocusChanged(c)) => Some(c),
248+
_ => None,
249+
};
250+
let no_change = |a: &Recording| a.drain().filter_map(f).count() == 0;
251+
let changed = |a: &Recording, b| a.drain().filter_map(f).eq(std::iter::once(b));
252+
253+
Harness::create_simple((), a, |harness| {
254+
harness.send_initial_events();
255+
256+
// focus none -> a
257+
harness.submit_command(TAKE_FOCUS, id_a);
258+
assert_eq!(harness.window().focus, Some(id_a));
259+
assert!(changed(&a_rec, true));
260+
assert!(no_change(&b_rec));
261+
assert!(no_change(&c_rec));
262+
263+
// focus a -> b
264+
harness.submit_command(TAKE_FOCUS, id_b);
265+
assert_eq!(harness.window().focus, Some(id_b));
266+
assert!(changed(&a_rec, false));
267+
assert!(changed(&b_rec, true));
268+
assert!(no_change(&c_rec));
269+
270+
// focus b -> c
271+
harness.submit_command(TAKE_FOCUS, id_c);
272+
assert_eq!(harness.window().focus, Some(id_c));
273+
assert!(no_change(&a_rec));
274+
assert!(changed(&b_rec, false));
275+
assert!(changed(&c_rec, true));
276+
277+
// focus c -> a
278+
harness.submit_command(TAKE_FOCUS, id_a);
279+
assert_eq!(harness.window().focus, Some(id_a));
280+
assert!(changed(&a_rec, true));
281+
assert!(no_change(&b_rec));
282+
assert!(changed(&c_rec, false));
283+
})
284+
}
285+
205286
#[test]
206287
fn simple_lifecyle() {
207288
let record = Recording::default();

0 commit comments

Comments
 (0)