From d09dfe01f5ad14f864063efd660f551f4377fff5 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 6 Dec 2023 16:15:53 +0100 Subject: [PATCH] Wire up global actions Added an ephemeral root node so that even if there's no window/focused handle we still have something to dispatch to. Co-authored-by: Antonio --- crates/editor2/src/element.rs | 63 ++++++----- crates/gpui2/src/app.rs | 6 +- crates/gpui2/src/elements/div.rs | 6 +- crates/gpui2/src/key_dispatch.rs | 25 +++-- crates/gpui2/src/window.rs | 186 +++++++++++++++++-------------- 5 files changed, 158 insertions(+), 128 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index d7b9d0bb40498cd8fb6c51f4cd33d5e6489f4ad1..ab11f5ffb5427d39905675756be5d9996e984fc1 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -2803,35 +2803,46 @@ impl Element for EditorElement { let focus_handle = editor.focus_handle(cx); let dispatch_context = self.editor.read(cx).dispatch_context(cx); - cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| { - self.register_actions(cx); - self.register_key_listeners(cx); - - // We call with_z_index to establish a new stacking context. - cx.with_z_index(0, |cx| { - cx.with_content_mask(Some(ContentMask { bounds }), |cx| { - // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor - // take precedence. - cx.with_z_index(0, |cx| { - self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); - }); - let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx); - cx.handle_input(&focus_handle, input_handler); + cx.with_key_dispatch( + Some(dispatch_context), + Some(focus_handle.clone()), + |_, cx| { + self.register_actions(cx); + self.register_key_listeners(cx); + + // We call with_z_index to establish a new stacking context. + cx.with_z_index(0, |cx| { + cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor + // take precedence. + cx.with_z_index(0, |cx| { + self.paint_mouse_listeners( + bounds, + gutter_bounds, + text_bounds, + &layout, + cx, + ); + }); + let input_handler = + ElementInputHandler::new(bounds, self.editor.clone(), cx); + cx.handle_input(&focus_handle, input_handler); - self.paint_background(gutter_bounds, text_bounds, &layout, cx); - if layout.gutter_size.width > Pixels::ZERO { - self.paint_gutter(gutter_bounds, &mut layout, cx); - } - self.paint_text(text_bounds, &mut layout, cx); + self.paint_background(gutter_bounds, text_bounds, &layout, cx); + if layout.gutter_size.width > Pixels::ZERO { + self.paint_gutter(gutter_bounds, &mut layout, cx); + } + self.paint_text(text_bounds, &mut layout, cx); - if !layout.blocks.is_empty() { - cx.with_element_id(Some("editor_blocks"), |cx| { - self.paint_blocks(bounds, &mut layout, cx); - }) - } + if !layout.blocks.is_empty() { + cx.with_element_id(Some("editor_blocks"), |cx| { + self.paint_blocks(bounds, &mut layout, cx); + }) + } + }); }); - }); - }) + }, + ) } } diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index fec6f150f6c341f916e0173379aba63bebcc1ffd..4803eb8b97c8cf86bfb56843f739670e1f81cb52 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -201,7 +201,7 @@ pub struct AppContext { pub(crate) windows: SlotMap>, pub(crate) keymap: Arc>, pub(crate) global_action_listeners: - HashMap>>, + HashMap>>, pending_effects: VecDeque, pub(crate) pending_notifications: HashSet, pub(crate) pending_global_notifications: HashSet, @@ -962,9 +962,9 @@ impl AppContext { self.global_action_listeners .entry(TypeId::of::()) .or_default() - .push(Box::new(move |action, phase, cx| { + .push(Rc::new(move |action, phase, cx| { if phase == DispatchPhase::Bubble { - let action = action.as_any().downcast_ref().unwrap(); + let action = action.downcast_ref().unwrap(); listener(action, cx) } })); diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index ce457fc6931246ee1e2e4d19a4a1639b37998395..c95a7f890f986b19ba7fdbac6b35c36ed8db36df 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -55,7 +55,7 @@ pub trait InteractiveElement: Sized + Element { E: Debug, { if let Some(key_context) = key_context.try_into().log_err() { - self.interactivity().key_context = key_context; + self.interactivity().key_context = Some(key_context); } self } @@ -722,7 +722,7 @@ impl DivState { pub struct Interactivity { pub element_id: Option, - pub key_context: KeyContext, + pub key_context: Option, pub focusable: bool, pub tracked_focus_handle: Option, pub scroll_handle: Option, @@ -1238,7 +1238,7 @@ impl Default for Interactivity { fn default() -> Self { Self { element_id: None, - key_context: KeyContext::default(), + key_context: None, focusable: false, tracked_focus_handle: None, scroll_handle: None, diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index 4838b1a612ce65ba33c03ac25da878a752f716d3..a79a358a1c38453fe7a8501e1ae0cae5ccf832c3 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -61,7 +61,7 @@ impl DispatchTree { self.keystroke_matchers.clear(); } - pub fn push_node(&mut self, context: KeyContext) { + pub fn push_node(&mut self, context: Option) { let parent = self.node_stack.last().copied(); let node_id = DispatchNodeId(self.nodes.len()); self.nodes.push(DispatchNode { @@ -69,7 +69,7 @@ impl DispatchTree { ..Default::default() }); self.node_stack.push(node_id); - if !context.is_empty() { + if let Some(context) = context { self.active_node().context = context.clone(); self.context_stack.push(context); } @@ -148,16 +148,14 @@ impl DispatchTree { false } - pub fn available_actions(&self, target: FocusId) -> Vec> { + pub fn available_actions(&self, target: DispatchNodeId) -> Vec> { let mut actions = Vec::new(); - if let Some(node) = self.focusable_node_ids.get(&target) { - for node_id in self.dispatch_path(*node) { - let node = &self.nodes[node_id.0]; - for DispatchActionListener { action_type, .. } in &node.action_listeners { - // Intentionally silence these errors without logging. - // If an action cannot be built by default, it's not available. - actions.extend(self.action_registry.build_action_type(action_type).ok()); - } + for node_id in self.dispatch_path(target) { + let node = &self.nodes[node_id.0]; + for DispatchActionListener { action_type, .. } in &node.action_listeners { + // Intentionally silence these errors without logging. + // If an action cannot be built by default, it's not available. + actions.extend(self.action_registry.build_action_type(action_type).ok()); } } actions @@ -236,6 +234,11 @@ impl DispatchTree { self.focusable_node_ids.get(&target).copied() } + pub fn root_node_id(&self) -> DispatchNodeId { + debug_assert!(!self.nodes.is_empty()); + DispatchNodeId(0) + } + fn active_node_id(&self) -> DispatchNodeId { *self.node_stack.last().unwrap() } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 271b09b8b950d777400f403fa59d5b81ad4e5ed6..6323eb962f101daa66fcfa8ae1482408507b7670 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -453,19 +453,21 @@ impl<'a> WindowContext<'a> { } pub fn dispatch_action(&mut self, action: Box) { - if let Some(focus_handle) = self.focused() { - self.defer(move |cx| { - if let Some(node_id) = cx - .window - .current_frame - .dispatch_tree - .focusable_node_id(focus_handle.id) - { - cx.propagate_event = true; - cx.dispatch_action_on_node(node_id, action); - } - }) - } + let focus_handle = self.focused(); + + self.defer(move |cx| { + let node_id = focus_handle + .and_then(|handle| { + cx.window + .current_frame + .dispatch_tree + .focusable_node_id(handle.id) + }) + .unwrap_or_else(|| cx.window.current_frame.dispatch_tree.root_node_id()); + + cx.propagate_event = true; + cx.dispatch_action_on_node(node_id, action); + }) } /// Schedules the given function to be run at the end of the current effect cycle, allowing entities @@ -1154,8 +1156,19 @@ impl<'a> WindowContext<'a> { self.start_frame(); self.with_z_index(0, |cx| { - let available_space = cx.window.viewport_size.map(Into::into); - root_view.draw(Point::zero(), available_space, cx); + cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| { + for (action_type, action_listeners) in &cx.app.global_action_listeners { + for action_listener in action_listeners.iter().cloned() { + cx.window.current_frame.dispatch_tree.on_action( + *action_type, + Rc::new(move |action, phase, cx| action_listener(action, phase, cx)), + ) + } + } + + let available_space = cx.window.viewport_size.map(Into::into); + root_view.draw(Point::zero(), available_space, cx); + }) }); if let Some(active_drag) = self.app.active_drag.take() { @@ -1338,73 +1351,77 @@ impl<'a> WindowContext<'a> { } fn dispatch_key_event(&mut self, event: &dyn Any) { - if let Some(node_id) = self.window.focus.and_then(|focus_id| { - self.window - .current_frame - .dispatch_tree - .focusable_node_id(focus_id) - }) { - let dispatch_path = self - .window - .current_frame - .dispatch_tree - .dispatch_path(node_id); + let node_id = self + .window + .focus + .and_then(|focus_id| { + self.window + .current_frame + .dispatch_tree + .focusable_node_id(focus_id) + }) + .unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id()); - let mut actions: Vec> = Vec::new(); + let dispatch_path = self + .window + .current_frame + .dispatch_tree + .dispatch_path(node_id); - // Capture phase - let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new(); - self.propagate_event = true; + let mut actions: Vec> = Vec::new(); - for node_id in &dispatch_path { - let node = self.window.current_frame.dispatch_tree.node(*node_id); + // Capture phase + let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new(); + self.propagate_event = true; - if !node.context.is_empty() { - context_stack.push(node.context.clone()); - } + for node_id in &dispatch_path { + let node = self.window.current_frame.dispatch_tree.node(*node_id); - for key_listener in node.key_listeners.clone() { - key_listener(event, DispatchPhase::Capture, self); - if !self.propagate_event { - return; - } + if !node.context.is_empty() { + context_stack.push(node.context.clone()); + } + + for key_listener in node.key_listeners.clone() { + key_listener(event, DispatchPhase::Capture, self); + if !self.propagate_event { + return; } } + } - // Bubble phase - for node_id in dispatch_path.iter().rev() { - // Handle low level key events - let node = self.window.current_frame.dispatch_tree.node(*node_id); - for key_listener in node.key_listeners.clone() { - key_listener(event, DispatchPhase::Bubble, self); - if !self.propagate_event { - return; - } + // Bubble phase + for node_id in dispatch_path.iter().rev() { + // Handle low level key events + let node = self.window.current_frame.dispatch_tree.node(*node_id); + for key_listener in node.key_listeners.clone() { + key_listener(event, DispatchPhase::Bubble, self); + if !self.propagate_event { + return; } + } - // Match keystrokes - let node = self.window.current_frame.dispatch_tree.node(*node_id); - if !node.context.is_empty() { - if let Some(key_down_event) = event.downcast_ref::() { - if let Some(found) = self - .window - .current_frame - .dispatch_tree - .dispatch_key(&key_down_event.keystroke, &context_stack) - { - actions.push(found.boxed_clone()) - } + // Match keystrokes + let node = self.window.current_frame.dispatch_tree.node(*node_id); + if !node.context.is_empty() { + if let Some(key_down_event) = event.downcast_ref::() { + if let Some(found) = self + .window + .current_frame + .dispatch_tree + .dispatch_key(&key_down_event.keystroke, &context_stack) + { + actions.push(found.boxed_clone()) } - - context_stack.pop(); } + + context_stack.pop(); } + } - for action in actions { - self.dispatch_action_on_node(node_id, action); - if !self.propagate_event { - return; - } + for action in actions { + self.dispatch_action_on_node(node_id, action); + if !self.propagate_event { + return; } } } @@ -1490,22 +1507,21 @@ impl<'a> WindowContext<'a> { } pub fn available_actions(&self) -> Vec> { - if let Some(focus_id) = self.window.focus { - let mut actions = self - .window - .current_frame - .dispatch_tree - .available_actions(focus_id); - actions.extend( - self.app - .global_action_listeners - .keys() - .filter_map(|type_id| self.app.actions.build_action_type(type_id).ok()), - ); - actions - } else { - Vec::new() - } + let node_id = self + .window + .focus + .and_then(|focus_id| { + self.window + .current_frame + .dispatch_tree + .focusable_node_id(focus_id) + }) + .unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id()); + + self.window + .current_frame + .dispatch_tree + .available_actions(node_id) } pub fn bindings_for_action(&self, action: &dyn Action) -> Vec { @@ -1561,7 +1577,7 @@ impl<'a> WindowContext<'a> { //========== ELEMENT RELATED FUNCTIONS =========== pub fn with_key_dispatch( &mut self, - context: KeyContext, + context: Option, focus_handle: Option, f: impl FnOnce(Option, &mut Self) -> R, ) -> R {