diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index d42a14bb77dd696f581116192eb384230300e831..b273c5914aa8242349f46a98db6b242450296c09 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -15,10 +15,10 @@ use crate::{ use anyhow::Result; use collections::{BTreeMap, HashMap}; use gpui::{ - black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, - BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, - ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler, - KeyContext, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton, + black, hsla, point, px, relative, size, transparent_black, Action, ActionListener, AnyElement, + AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, + Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, + InputHandler, KeyContext, KeyDownEvent, KeyMatch, Line, LineLayout, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size, Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout, }; @@ -2459,7 +2459,166 @@ impl Element for EditorElement { cx.with_key_dispatch( dispatch_context, Some(editor.focus_handle.clone()), - |_, _| {}, + |_, cx| { + handle_action(cx, Editor::move_left); + handle_action(cx, Editor::move_right); + handle_action(cx, Editor::move_down); + handle_action(cx, Editor::move_up); + // on_action(cx, Editor::new_file); todo!() + // on_action(cx, Editor::new_file_in_direction); todo!() + handle_action(cx, Editor::cancel); + handle_action(cx, Editor::newline); + handle_action(cx, Editor::newline_above); + handle_action(cx, Editor::newline_below); + handle_action(cx, Editor::backspace); + handle_action(cx, Editor::delete); + handle_action(cx, Editor::tab); + handle_action(cx, Editor::tab_prev); + handle_action(cx, Editor::indent); + handle_action(cx, Editor::outdent); + handle_action(cx, Editor::delete_line); + handle_action(cx, Editor::join_lines); + handle_action(cx, Editor::sort_lines_case_sensitive); + handle_action(cx, Editor::sort_lines_case_insensitive); + handle_action(cx, Editor::reverse_lines); + handle_action(cx, Editor::shuffle_lines); + handle_action(cx, Editor::convert_to_upper_case); + handle_action(cx, Editor::convert_to_lower_case); + handle_action(cx, Editor::convert_to_title_case); + handle_action(cx, Editor::convert_to_snake_case); + handle_action(cx, Editor::convert_to_kebab_case); + handle_action(cx, Editor::convert_to_upper_camel_case); + handle_action(cx, Editor::convert_to_lower_camel_case); + handle_action(cx, Editor::delete_to_previous_word_start); + handle_action(cx, Editor::delete_to_previous_subword_start); + handle_action(cx, Editor::delete_to_next_word_end); + handle_action(cx, Editor::delete_to_next_subword_end); + handle_action(cx, Editor::delete_to_beginning_of_line); + handle_action(cx, Editor::delete_to_end_of_line); + handle_action(cx, Editor::cut_to_end_of_line); + handle_action(cx, Editor::duplicate_line); + handle_action(cx, Editor::move_line_up); + handle_action(cx, Editor::move_line_down); + handle_action(cx, Editor::transpose); + handle_action(cx, Editor::cut); + handle_action(cx, Editor::copy); + handle_action(cx, Editor::paste); + handle_action(cx, Editor::undo); + handle_action(cx, Editor::redo); + handle_action(cx, Editor::move_page_up); + handle_action(cx, Editor::move_page_down); + handle_action(cx, Editor::next_screen); + handle_action(cx, Editor::scroll_cursor_top); + handle_action(cx, Editor::scroll_cursor_center); + handle_action(cx, Editor::scroll_cursor_bottom); + handle_action(cx, |editor, _: &LineDown, cx| { + editor.scroll_screen(&ScrollAmount::Line(1.), cx) + }); + handle_action(cx, |editor, _: &LineUp, cx| { + editor.scroll_screen(&ScrollAmount::Line(-1.), cx) + }); + handle_action(cx, |editor, _: &HalfPageDown, cx| { + editor.scroll_screen(&ScrollAmount::Page(0.5), cx) + }); + handle_action(cx, |editor, _: &HalfPageUp, cx| { + editor.scroll_screen(&ScrollAmount::Page(-0.5), cx) + }); + handle_action(cx, |editor, _: &PageDown, cx| { + editor.scroll_screen(&ScrollAmount::Page(1.), cx) + }); + handle_action(cx, |editor, _: &PageUp, cx| { + editor.scroll_screen(&ScrollAmount::Page(-1.), cx) + }); + handle_action(cx, Editor::move_to_previous_word_start); + handle_action(cx, Editor::move_to_previous_subword_start); + handle_action(cx, Editor::move_to_next_word_end); + handle_action(cx, Editor::move_to_next_subword_end); + handle_action(cx, Editor::move_to_beginning_of_line); + handle_action(cx, Editor::move_to_end_of_line); + handle_action(cx, Editor::move_to_start_of_paragraph); + handle_action(cx, Editor::move_to_end_of_paragraph); + handle_action(cx, Editor::move_to_beginning); + handle_action(cx, Editor::move_to_end); + handle_action(cx, Editor::select_up); + handle_action(cx, Editor::select_down); + handle_action(cx, Editor::select_left); + handle_action(cx, Editor::select_right); + handle_action(cx, Editor::select_to_previous_word_start); + handle_action(cx, Editor::select_to_previous_subword_start); + handle_action(cx, Editor::select_to_next_word_end); + handle_action(cx, Editor::select_to_next_subword_end); + handle_action(cx, Editor::select_to_beginning_of_line); + handle_action(cx, Editor::select_to_end_of_line); + handle_action(cx, Editor::select_to_start_of_paragraph); + handle_action(cx, Editor::select_to_end_of_paragraph); + handle_action(cx, Editor::select_to_beginning); + handle_action(cx, Editor::select_to_end); + handle_action(cx, Editor::select_all); + handle_action(cx, |editor, action, cx| { + editor.select_all_matches(action, cx).log_err(); + }); + handle_action(cx, Editor::select_line); + handle_action(cx, Editor::split_selection_into_lines); + handle_action(cx, Editor::add_selection_above); + handle_action(cx, Editor::add_selection_below); + handle_action(cx, |editor, action, cx| { + editor.select_next(action, cx).log_err(); + }); + handle_action(cx, |editor, action, cx| { + editor.select_previous(action, cx).log_err(); + }); + handle_action(cx, Editor::toggle_comments); + handle_action(cx, Editor::select_larger_syntax_node); + handle_action(cx, Editor::select_smaller_syntax_node); + handle_action(cx, Editor::move_to_enclosing_bracket); + handle_action(cx, Editor::undo_selection); + handle_action(cx, Editor::redo_selection); + handle_action(cx, Editor::go_to_diagnostic); + handle_action(cx, Editor::go_to_prev_diagnostic); + handle_action(cx, Editor::go_to_hunk); + handle_action(cx, Editor::go_to_prev_hunk); + handle_action(cx, Editor::go_to_definition); + handle_action(cx, Editor::go_to_definition_split); + handle_action(cx, Editor::go_to_type_definition); + handle_action(cx, Editor::go_to_type_definition_split); + handle_action(cx, Editor::fold); + handle_action(cx, Editor::fold_at); + handle_action(cx, Editor::unfold_lines); + handle_action(cx, Editor::unfold_at); + handle_action(cx, Editor::fold_selected_ranges); + handle_action(cx, Editor::show_completions); + handle_action(cx, Editor::toggle_code_actions); + // on_action(cx, Editor::open_excerpts); todo!() + handle_action(cx, Editor::toggle_soft_wrap); + handle_action(cx, Editor::toggle_inlay_hints); + handle_action(cx, Editor::reveal_in_finder); + handle_action(cx, Editor::copy_path); + handle_action(cx, Editor::copy_relative_path); + handle_action(cx, Editor::copy_highlight_json); + handle_action(cx, |editor, action, cx| { + editor + .format(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }); + handle_action(cx, Editor::restart_language_server); + handle_action(cx, Editor::show_character_palette); + // on_action(cx, Editor::confirm_completion); todo!() + handle_action(cx, |editor, action, cx| { + editor + .confirm_code_action(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }); + // on_action(cx, Editor::rename); todo!() + // on_action(cx, Editor::confirm_rename); todo!() + // on_action(cx, Editor::find_all_references); todo!() + handle_action(cx, Editor::next_copilot_suggestion); + handle_action(cx, Editor::previous_copilot_suggestion); + handle_action(cx, Editor::copilot_suggest); + handle_action(cx, Editor::context_menu_first); + handle_action(cx, Editor::context_menu_prev); + handle_action(cx, Editor::context_menu_next); + handle_action(cx, Editor::context_menu_last); + }, ) }); } @@ -3995,197 +4154,14 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 { // } // } -fn build_key_listeners( - global_element_id: GlobalElementId, -) -> impl IntoIterator)> { - [ - build_action_listener(Editor::move_left), - build_action_listener(Editor::move_right), - build_action_listener(Editor::move_down), - build_action_listener(Editor::move_up), - // build_action_listener(Editor::new_file), todo!() - // build_action_listener(Editor::new_file_in_direction), todo!() - build_action_listener(Editor::cancel), - build_action_listener(Editor::newline), - build_action_listener(Editor::newline_above), - build_action_listener(Editor::newline_below), - build_action_listener(Editor::backspace), - build_action_listener(Editor::delete), - build_action_listener(Editor::tab), - build_action_listener(Editor::tab_prev), - build_action_listener(Editor::indent), - build_action_listener(Editor::outdent), - build_action_listener(Editor::delete_line), - build_action_listener(Editor::join_lines), - build_action_listener(Editor::sort_lines_case_sensitive), - build_action_listener(Editor::sort_lines_case_insensitive), - build_action_listener(Editor::reverse_lines), - build_action_listener(Editor::shuffle_lines), - build_action_listener(Editor::convert_to_upper_case), - build_action_listener(Editor::convert_to_lower_case), - build_action_listener(Editor::convert_to_title_case), - build_action_listener(Editor::convert_to_snake_case), - build_action_listener(Editor::convert_to_kebab_case), - build_action_listener(Editor::convert_to_upper_camel_case), - build_action_listener(Editor::convert_to_lower_camel_case), - build_action_listener(Editor::delete_to_previous_word_start), - build_action_listener(Editor::delete_to_previous_subword_start), - build_action_listener(Editor::delete_to_next_word_end), - build_action_listener(Editor::delete_to_next_subword_end), - build_action_listener(Editor::delete_to_beginning_of_line), - build_action_listener(Editor::delete_to_end_of_line), - build_action_listener(Editor::cut_to_end_of_line), - build_action_listener(Editor::duplicate_line), - build_action_listener(Editor::move_line_up), - build_action_listener(Editor::move_line_down), - build_action_listener(Editor::transpose), - build_action_listener(Editor::cut), - build_action_listener(Editor::copy), - build_action_listener(Editor::paste), - build_action_listener(Editor::undo), - build_action_listener(Editor::redo), - build_action_listener(Editor::move_page_up), - build_action_listener(Editor::move_page_down), - build_action_listener(Editor::next_screen), - build_action_listener(Editor::scroll_cursor_top), - build_action_listener(Editor::scroll_cursor_center), - build_action_listener(Editor::scroll_cursor_bottom), - build_action_listener(|editor, _: &LineDown, cx| { - editor.scroll_screen(&ScrollAmount::Line(1.), cx) - }), - build_action_listener(|editor, _: &LineUp, cx| { - editor.scroll_screen(&ScrollAmount::Line(-1.), cx) - }), - build_action_listener(|editor, _: &HalfPageDown, cx| { - editor.scroll_screen(&ScrollAmount::Page(0.5), cx) - }), - build_action_listener(|editor, _: &HalfPageUp, cx| { - editor.scroll_screen(&ScrollAmount::Page(-0.5), cx) - }), - build_action_listener(|editor, _: &PageDown, cx| { - editor.scroll_screen(&ScrollAmount::Page(1.), cx) - }), - build_action_listener(|editor, _: &PageUp, cx| { - editor.scroll_screen(&ScrollAmount::Page(-1.), cx) - }), - build_action_listener(Editor::move_to_previous_word_start), - build_action_listener(Editor::move_to_previous_subword_start), - build_action_listener(Editor::move_to_next_word_end), - build_action_listener(Editor::move_to_next_subword_end), - build_action_listener(Editor::move_to_beginning_of_line), - build_action_listener(Editor::move_to_end_of_line), - build_action_listener(Editor::move_to_start_of_paragraph), - build_action_listener(Editor::move_to_end_of_paragraph), - build_action_listener(Editor::move_to_beginning), - build_action_listener(Editor::move_to_end), - build_action_listener(Editor::select_up), - build_action_listener(Editor::select_down), - build_action_listener(Editor::select_left), - build_action_listener(Editor::select_right), - build_action_listener(Editor::select_to_previous_word_start), - build_action_listener(Editor::select_to_previous_subword_start), - build_action_listener(Editor::select_to_next_word_end), - build_action_listener(Editor::select_to_next_subword_end), - build_action_listener(Editor::select_to_beginning_of_line), - build_action_listener(Editor::select_to_end_of_line), - build_action_listener(Editor::select_to_start_of_paragraph), - build_action_listener(Editor::select_to_end_of_paragraph), - build_action_listener(Editor::select_to_beginning), - build_action_listener(Editor::select_to_end), - build_action_listener(Editor::select_all), - build_action_listener(|editor, action, cx| { - editor.select_all_matches(action, cx).log_err(); - }), - build_action_listener(Editor::select_line), - build_action_listener(Editor::split_selection_into_lines), - build_action_listener(Editor::add_selection_above), - build_action_listener(Editor::add_selection_below), - build_action_listener(|editor, action, cx| { - editor.select_next(action, cx).log_err(); - }), - build_action_listener(|editor, action, cx| { - editor.select_previous(action, cx).log_err(); - }), - build_action_listener(Editor::toggle_comments), - build_action_listener(Editor::select_larger_syntax_node), - build_action_listener(Editor::select_smaller_syntax_node), - build_action_listener(Editor::move_to_enclosing_bracket), - build_action_listener(Editor::undo_selection), - build_action_listener(Editor::redo_selection), - build_action_listener(Editor::go_to_diagnostic), - build_action_listener(Editor::go_to_prev_diagnostic), - build_action_listener(Editor::go_to_hunk), - build_action_listener(Editor::go_to_prev_hunk), - build_action_listener(Editor::go_to_definition), - build_action_listener(Editor::go_to_definition_split), - build_action_listener(Editor::go_to_type_definition), - build_action_listener(Editor::go_to_type_definition_split), - build_action_listener(Editor::fold), - build_action_listener(Editor::fold_at), - build_action_listener(Editor::unfold_lines), - build_action_listener(Editor::unfold_at), - build_action_listener(Editor::fold_selected_ranges), - build_action_listener(Editor::show_completions), - build_action_listener(Editor::toggle_code_actions), - // build_action_listener(Editor::open_excerpts), todo!() - build_action_listener(Editor::toggle_soft_wrap), - build_action_listener(Editor::toggle_inlay_hints), - build_action_listener(Editor::reveal_in_finder), - build_action_listener(Editor::copy_path), - build_action_listener(Editor::copy_relative_path), - build_action_listener(Editor::copy_highlight_json), - build_action_listener(|editor, action, cx| { - editor - .format(action, cx) - .map(|task| task.detach_and_log_err(cx)); - }), - build_action_listener(Editor::restart_language_server), - build_action_listener(Editor::show_character_palette), - // build_action_listener(Editor::confirm_completion), todo!() - build_action_listener(|editor, action, cx| { - editor - .confirm_code_action(action, cx) - .map(|task| task.detach_and_log_err(cx)); - }), - // build_action_listener(Editor::rename), todo!() - // build_action_listener(Editor::confirm_rename), todo!() - // build_action_listener(Editor::find_all_references), todo!() - build_action_listener(Editor::next_copilot_suggestion), - build_action_listener(Editor::previous_copilot_suggestion), - build_action_listener(Editor::copilot_suggest), - build_action_listener(Editor::context_menu_first), - build_action_listener(Editor::context_menu_prev), - build_action_listener(Editor::context_menu_next), - build_action_listener(Editor::context_menu_last), - ] -} - -fn build_key_listener( - listener: impl Fn( - &mut Editor, - &T, - &[&KeyContext], - DispatchPhase, - &mut ViewContext, - ) -> Option> - + 'static, -) -> (TypeId, KeyListener) { - ( - TypeId::of::(), - Box::new(move |editor, event, dispatch_context, phase, cx| { - let key_event = event.downcast_ref::()?; - listener(editor, key_event, dispatch_context, phase, cx) - }), - ) -} - -fn build_action_listener( +fn handle_action( + cx: &mut ViewContext, listener: impl Fn(&mut Editor, &T, &mut ViewContext) + 'static, -) -> (TypeId, KeyListener) { - build_key_listener(move |editor, action: &T, dispatch_context, phase, cx| { +) { + cx.on_action(TypeId::of::(), move |editor, action, phase, cx| { + let action = action.downcast_ref().unwrap(); if phase == DispatchPhase::Bubble { listener(editor, action, cx); } - None }) } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index eaac9fc71ecc1ac16ab38c9f917c3937c665ac9f..7bfd4b244a00bd6211df39d72b81d16f358c422a 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -234,6 +234,7 @@ where element_state.focus_handle.take(), cx, |focus_handle, cx| { + this.interactivity.initialize(cx); element_state.focus_handle = focus_handle; for child in &mut this.children { child.initialize(view_state, cx); diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 9ac1f560998eaf64a57e88386c2a13c2dc65c689..4a7633f8dc6299af9b60eeabdb307ba1b036186c 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -165,43 +165,40 @@ pub trait StatelessInteractive: Element { } /// Capture the given action, fires during the capture phase - fn capture_action( + fn capture_action( mut self, listener: impl Fn(&mut V, &A, &mut ViewContext) + 'static, ) -> Self where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( + self.stateless_interactivity().action_listeners.push(( TypeId::of::(), - Box::new(move |view, action, _dipatch_context, phase, cx| { + Box::new(move |view, action, phase, cx| { let action = action.downcast_ref().unwrap(); if phase == DispatchPhase::Capture { listener(view, action, cx) } - None }), )); self } /// Add a listener for the given action, fires during the bubble event phase - fn on_action( + fn on_action( mut self, listener: impl Fn(&mut V, &A, &mut ViewContext) + 'static, ) -> Self where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( + self.stateless_interactivity().action_listeners.push(( TypeId::of::(), - Box::new(move |view, action, _dispatch_context, phase, cx| { + Box::new(move |view, action, phase, cx| { let action = action.downcast_ref().unwrap(); if phase == DispatchPhase::Bubble { listener(view, action, cx) } - - None }), )); self @@ -214,14 +211,11 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( - TypeId::of::(), - Box::new(move |view, event, _, phase, cx| { - let event = event.downcast_ref().unwrap(); - listener(view, event, phase, cx); - None - }), - )); + self.stateless_interactivity() + .key_down_listeners + .push(Box::new(move |view, event, phase, cx| { + listener(view, event, phase, cx) + })); self } @@ -232,14 +226,11 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( - TypeId::of::(), - Box::new(move |view, event, _, phase, cx| { - let event = event.downcast_ref().unwrap(); - listener(view, event, phase, cx); - None - }), - )); + self.stateless_interactivity() + .key_up_listeners + .push(Box::new(move |view, event, phase, cx| { + listener(view, event, phase, cx) + })); self } @@ -439,6 +430,26 @@ pub trait ElementInteractivity: 'static { } } + fn initialize(&mut self, cx: &mut ViewContext) { + let stateless = self.as_stateless_mut(); + + for listener in stateless.key_down_listeners.drain(..) { + cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| { + listener(state, event, phase, cx); + }) + } + + for listener in stateless.key_up_listeners.drain(..) { + cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| { + listener(state, event, phase, cx); + }) + } + + for (action_type, listener) in stateless.action_listeners.drain(..) { + cx.on_action(action_type, listener) + } + } + fn paint( &mut self, bounds: Bounds, @@ -765,7 +776,9 @@ pub struct StatelessInteractivity { pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>, pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>, pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener; 2]>, - pub key_listeners: SmallVec<[(TypeId, KeyListener); 32]>, + pub key_down_listeners: SmallVec<[KeyDownListener; 2]>, + pub key_up_listeners: SmallVec<[KeyUpListener; 2]>, + pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>, pub hover_style: StyleRefinement, pub group_hover_style: Option, drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>, @@ -867,7 +880,9 @@ impl Default for StatelessInteractivity { mouse_up_listeners: SmallVec::new(), mouse_move_listeners: SmallVec::new(), scroll_wheel_listeners: SmallVec::new(), - key_listeners: SmallVec::new(), + key_down_listeners: SmallVec::new(), + key_up_listeners: SmallVec::new(), + action_listeners: SmallVec::new(), hover_style: StyleRefinement::default(), group_hover_style: None, drag_over_styles: SmallVec::new(), @@ -1202,16 +1217,14 @@ pub(crate) type HoverListener = Box) pub(crate) type TooltipBuilder = Arc) -> AnyView + 'static>; -pub type KeyListener = Box< - dyn Fn( - &mut V, - &dyn Any, - &[&KeyContext], - DispatchPhase, - &mut ViewContext, - ) -> Option> - + 'static, ->; +pub(crate) type KeyDownListener = + Box) + 'static>; + +pub(crate) type KeyUpListener = + Box) + 'static>; + +pub type ActionListener = + Box) + 'static>; #[cfg(test)] mod test { diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index 9f76df82c3024e2123f4d4141e432865e7c871d5..40d6c66973fdd3acaf7ce5159c506175751ee0fe 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -69,6 +69,7 @@ impl KeyDispatcher { }); self.node_stack.push(node_id); if !context.is_empty() { + self.active_node().context = context.clone(); self.context_stack.push(context); if let Some((context_stack, matcher)) = old_dispatcher .keystroke_matchers @@ -153,6 +154,7 @@ impl KeyDispatcher { // Capture phase self.context_stack.clear(); cx.propagate_event = true; + for node_id in &dispatch_path { let node = &self.nodes[node_id.0]; if !node.context.is_empty() { @@ -193,18 +195,16 @@ impl KeyDispatcher { ); } - if let Some(keystroke_matcher) = self + let keystroke_matcher = self .keystroke_matchers .get_mut(self.context_stack.as_slice()) + .unwrap(); + if let KeyMatch::Some(action) = keystroke_matcher + .match_keystroke(&key_down_event.keystroke, self.context_stack.as_slice()) { - if let KeyMatch::Some(action) = keystroke_matcher.match_keystroke( - &key_down_event.keystroke, - self.context_stack.as_slice(), - ) { - self.dispatch_action_on_node(*node_id, action, cx); - if !cx.propagate_event { - return; - } + self.dispatch_action_on_node(*node_id, action, cx); + if !cx.propagate_event { + return; } } } @@ -236,10 +236,17 @@ impl KeyDispatcher { // Capture phase for node_id in &dispatch_path { let node = &self.nodes[node_id.0]; - for ActionListener { listener, .. } in &node.action_listeners { - listener(&action, DispatchPhase::Capture, cx); - if !cx.propagate_event { - return; + for ActionListener { + action_type, + listener, + } in &node.action_listeners + { + let any_action = action.as_any(); + if *action_type == any_action.type_id() { + listener(any_action, DispatchPhase::Capture, cx); + if !cx.propagate_event { + return; + } } } } @@ -247,11 +254,18 @@ impl KeyDispatcher { // Bubble phase for node_id in dispatch_path.iter().rev() { let node = &self.nodes[node_id.0]; - for ActionListener { listener, .. } in &node.action_listeners { - cx.propagate_event = false; // Actions stop propagation by default during the bubble phase - listener(&action, DispatchPhase::Capture, cx); - if !cx.propagate_event { - return; + for ActionListener { + action_type, + listener, + } in &node.action_listeners + { + let any_action = action.as_any(); + if *action_type == any_action.type_id() { + cx.propagate_event = false; // Actions stop propagation by default during the bubble phase + listener(any_action, DispatchPhase::Bubble, cx); + if !cx.propagate_event { + return; + } } } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 3da2664f7938503bf6ba846165fe793b3dff70b5..82d5982475f743c951f8b241c5b1d1fd870b82b5 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -713,6 +713,42 @@ impl<'a> WindowContext<'a> { )) } + /// Register a key event listener on the window for the current frame. The type of event + /// is determined by the first parameter of the given listener. When the next frame is rendered + /// the listener will be cleared. + /// + /// This is a fairly low-level method, so prefer using event handlers on elements unless you have + /// a specific need to register a global listener. + pub fn on_key_event( + &mut self, + handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, + ) { + let key_dispatcher = self.window.current_frame.key_dispatcher.as_mut().unwrap(); + key_dispatcher.on_key_event(Box::new(move |event, phase, cx| { + if let Some(event) = event.downcast_ref::() { + handler(event, phase, cx) + } + })); + } + + /// Register an action listener on the window for the current frame. The type of action + /// is determined by the first parameter of the given listener. When the next frame is rendered + /// the listener will be cleared. + /// + /// This is a fairly low-level method, so prefer using action handlers on elements unless you have + /// a specific need to register a global listener. + pub fn on_action( + &mut self, + action_type: TypeId, + handler: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static, + ) { + let key_dispatcher = self.window.current_frame.key_dispatcher.as_mut().unwrap(); + key_dispatcher.on_action( + action_type, + Box::new(move |action, phase, cx| handler(action, phase, cx)), + ); + } + /// The position of the mouse relative to the window. pub fn mouse_position(&self) -> Point { self.window.mouse_position @@ -1955,6 +1991,32 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + pub fn on_key_event( + &mut self, + handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, + ) { + let handle = self.view(); + self.window_cx.on_key_event(move |event, phase, cx| { + handle.update(cx, |view, cx| { + handler(view, event, phase, cx); + }) + }); + } + + pub fn on_action( + &mut self, + action_type: TypeId, + handler: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext) + 'static, + ) { + let handle = self.view(); + self.window_cx + .on_action(action_type, move |action, phase, cx| { + handle.update(cx, |view, cx| { + handler(view, action, phase, cx); + }) + }); + } + /// Set an input handler, such as [ElementInputHandler], which interfaces with the /// platform to receive textual input with proper integration with concerns such /// as IME interactions. diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index 368fb20fbf9c6e83ec5616223baf6a89bdcedb9d..142e71cde43a377602b6f408daf53d3a9b257f62 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -39,10 +39,10 @@ impl Render for FocusStory { .focusable() .context("parent") .on_action(|_, action: &ActionA, cx| { - println!("Action A dispatched on parent during"); + println!("Action A dispatched on parent"); }) .on_action(|_, action: &ActionB, cx| { - println!("Action B dispatched on parent during"); + println!("Action B dispatched on parent"); }) .on_focus(|_, _, _| println!("Parent focused")) .on_blur(|_, _, _| println!("Parent blurred")) @@ -79,7 +79,7 @@ impl Render for FocusStory { .track_focus(&child_2) .context("child-2") .on_action(|_, action: &ActionC, cx| { - println!("Action C dispatched on child 2 during"); + println!("Action C dispatched on child 2"); }) .w_full() .h_6()