diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index dec73ab6c2..00b1ef2de5 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -722,6 +722,7 @@ pub struct MutableAppContext { foreground_platform: Rc, assets: Arc, cx: AppContext, + capture_actions: HashMap>>>, actions: HashMap>>>, global_actions: HashMap>, keystroke_matcher: keymap::Matcher, @@ -768,6 +769,7 @@ impl MutableAppContext { font_cache, platform, }, + capture_actions: HashMap::new(), actions: HashMap::new(), global_actions: HashMap::new(), keystroke_matcher: keymap::Matcher::default(), @@ -857,7 +859,25 @@ impl MutableAppContext { .map(|debug_elements| debug_elements(&self.cx)) } - pub fn add_action(&mut self, mut handler: F) + pub fn add_action(&mut self, handler: F) + where + A: Action, + V: View, + F: 'static + FnMut(&mut V, &A, &mut ViewContext), + { + self.add_action_internal(handler, false) + } + + pub fn capture_action(&mut self, handler: F) + where + A: Action, + V: View, + F: 'static + FnMut(&mut V, &A, &mut ViewContext), + { + self.add_action_internal(handler, true) + } + + fn add_action_internal(&mut self, mut handler: F, capture: bool) where A: Action, V: View, @@ -881,7 +901,13 @@ impl MutableAppContext { }, ); - self.actions + let actions = if capture { + &mut self.capture_actions + } else { + &mut self.actions + }; + + actions .entry(TypeId::of::()) .or_default() .entry(TypeId::of::()) @@ -1169,29 +1195,33 @@ impl MutableAppContext { ) -> bool { self.update(|this| { this.halt_action_dispatch = false; - for view_id in path.iter().rev() { - if let Some(mut view) = this.cx.views.remove(&(window_id, *view_id)) { + for (capture_phase, view_id) in path + .iter() + .map(|view_id| (true, *view_id)) + .chain(path.iter().rev().map(|view_id| (false, *view_id))) + { + if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) { let type_id = view.as_any().type_id(); if let Some((name, mut handlers)) = this - .actions + .actions_mut(capture_phase) .get_mut(&type_id) .and_then(|h| h.remove_entry(&action.id())) { for handler in handlers.iter_mut().rev() { this.halt_action_dispatch = true; - handler(view.as_mut(), action, this, window_id, *view_id); + handler(view.as_mut(), action, this, window_id, view_id); if this.halt_action_dispatch { break; } } - this.actions + this.actions_mut(capture_phase) .get_mut(&type_id) .unwrap() .insert(name, handlers); } - this.cx.views.insert((window_id, *view_id), view); + this.cx.views.insert((window_id, view_id), view); if this.halt_action_dispatch { break; @@ -1206,6 +1236,17 @@ impl MutableAppContext { }) } + fn actions_mut( + &mut self, + capture_phase: bool, + ) -> &mut HashMap>>> { + if capture_phase { + &mut self.capture_actions + } else { + &mut self.actions + } + } + pub fn dispatch_global_action(&mut self, action: A) { self.dispatch_global_action_any(&action); } @@ -4320,40 +4361,58 @@ mod tests { let actions = Rc::new(RefCell::new(Vec::new())); - let actions_clone = actions.clone(); - cx.add_global_action(move |_: &Action, _: &mut MutableAppContext| { - actions_clone.borrow_mut().push("global".to_string()); - }); + { + let actions = actions.clone(); + cx.add_global_action(move |_: &Action, _: &mut MutableAppContext| { + actions.borrow_mut().push("global".to_string()); + }); + } - let actions_clone = actions.clone(); - cx.add_action(move |view: &mut ViewA, action: &Action, cx| { - assert_eq!(action.0, "bar"); - cx.propagate_action(); - actions_clone.borrow_mut().push(format!("{} a", view.id)); - }); + { + let actions = actions.clone(); + cx.add_action(move |view: &mut ViewA, action: &Action, cx| { + assert_eq!(action.0, "bar"); + cx.propagate_action(); + actions.borrow_mut().push(format!("{} a", view.id)); + }); + } - let actions_clone = actions.clone(); - cx.add_action(move |view: &mut ViewA, _: &Action, cx| { - if view.id != 1 { - cx.add_view(|cx| { - cx.propagate_action(); // Still works on a nested ViewContext - ViewB { id: 5 } - }); - } - actions_clone.borrow_mut().push(format!("{} b", view.id)); - }); + { + let actions = actions.clone(); + cx.add_action(move |view: &mut ViewA, _: &Action, cx| { + if view.id != 1 { + cx.add_view(|cx| { + cx.propagate_action(); // Still works on a nested ViewContext + ViewB { id: 5 } + }); + } + actions.borrow_mut().push(format!("{} b", view.id)); + }); + } - let actions_clone = actions.clone(); - cx.add_action(move |view: &mut ViewB, _: &Action, cx| { - cx.propagate_action(); - actions_clone.borrow_mut().push(format!("{} c", view.id)); - }); + { + let actions = actions.clone(); + cx.add_action(move |view: &mut ViewB, _: &Action, cx| { + cx.propagate_action(); + actions.borrow_mut().push(format!("{} c", view.id)); + }); + } - let actions_clone = actions.clone(); - cx.add_action(move |view: &mut ViewB, _: &Action, cx| { - cx.propagate_action(); - actions_clone.borrow_mut().push(format!("{} d", view.id)); - }); + { + let actions = actions.clone(); + cx.add_action(move |view: &mut ViewB, _: &Action, cx| { + cx.propagate_action(); + actions.borrow_mut().push(format!("{} d", view.id)); + }); + } + + { + let actions = actions.clone(); + cx.capture_action(move |view: &mut ViewA, _: &Action, cx| { + cx.propagate_action(); + actions.borrow_mut().push(format!("{} capture", view.id)); + }); + } let (window_id, view_1) = cx.add_window(Default::default(), |_| ViewA { id: 1 }); let view_2 = cx.add_view(window_id, |_| ViewB { id: 2 }); @@ -4368,7 +4427,17 @@ mod tests { assert_eq!( *actions.borrow(), - vec!["4 d", "4 c", "3 b", "3 a", "2 d", "2 c", "1 b"] + vec![ + "1 capture", + "3 capture", + "4 d", + "4 c", + "3 b", + "3 a", + "2 d", + "2 c", + "1 b" + ] ); // Remove view_1, which doesn't propagate the action @@ -4381,7 +4450,16 @@ mod tests { assert_eq!( *actions.borrow(), - vec!["4 d", "4 c", "3 b", "3 a", "2 d", "2 c", "global"] + vec![ + "3 capture", + "4 d", + "4 c", + "3 b", + "3 a", + "2 d", + "2 c", + "global" + ] ); }