Register key and action listeners using Interactive::initialize
Co-Authored-By: Thorsten <mrnugget@gmail.com>
This commit is contained in:
co-authored by
Thorsten
parent
318cb784b2
commit
9c18253863
+169
-193
@@ -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<Editor> 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<Item = (TypeId, KeyListener<Editor>)> {
|
||||
[
|
||||
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<T: 'static>(
|
||||
listener: impl Fn(
|
||||
&mut Editor,
|
||||
&T,
|
||||
&[&KeyContext],
|
||||
DispatchPhase,
|
||||
&mut ViewContext<Editor>,
|
||||
) -> Option<Box<dyn Action>>
|
||||
+ 'static,
|
||||
) -> (TypeId, KeyListener<Editor>) {
|
||||
(
|
||||
TypeId::of::<T>(),
|
||||
Box::new(move |editor, event, dispatch_context, phase, cx| {
|
||||
let key_event = event.downcast_ref::<T>()?;
|
||||
listener(editor, key_event, dispatch_context, phase, cx)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn build_action_listener<T: Action>(
|
||||
fn handle_action<T: Action>(
|
||||
cx: &mut ViewContext<Editor>,
|
||||
listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
|
||||
) -> (TypeId, KeyListener<Editor>) {
|
||||
build_key_listener(move |editor, action: &T, dispatch_context, phase, cx| {
|
||||
) {
|
||||
cx.on_action(TypeId::of::<T>(), move |editor, action, phase, cx| {
|
||||
let action = action.downcast_ref().unwrap();
|
||||
if phase == DispatchPhase::Bubble {
|
||||
listener(editor, action, cx);
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -165,43 +165,40 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
}
|
||||
|
||||
/// Capture the given action, fires during the capture phase
|
||||
fn capture_action<A: 'static>(
|
||||
fn capture_action<A: Action>(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interactivity().key_listeners.push((
|
||||
self.stateless_interactivity().action_listeners.push((
|
||||
TypeId::of::<A>(),
|
||||
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<A: 'static>(
|
||||
fn on_action<A: Action>(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interactivity().key_listeners.push((
|
||||
self.stateless_interactivity().action_listeners.push((
|
||||
TypeId::of::<A>(),
|
||||
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<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interactivity().key_listeners.push((
|
||||
TypeId::of::<KeyDownEvent>(),
|
||||
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<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interactivity().key_listeners.push((
|
||||
TypeId::of::<KeyUpEvent>(),
|
||||
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<V: 'static>: 'static {
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(&mut self, cx: &mut ViewContext<V>) {
|
||||
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<Pixels>,
|
||||
@@ -765,7 +776,9 @@ pub struct StatelessInteractivity<V> {
|
||||
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
|
||||
pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
|
||||
pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
|
||||
pub key_listeners: SmallVec<[(TypeId, KeyListener<V>); 32]>,
|
||||
pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,
|
||||
pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,
|
||||
pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,
|
||||
pub hover_style: StyleRefinement,
|
||||
pub group_hover_style: Option<GroupStyle>,
|
||||
drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
|
||||
@@ -867,7 +880,9 @@ impl<V> Default for StatelessInteractivity<V> {
|
||||
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<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>)
|
||||
|
||||
pub(crate) type TooltipBuilder<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
|
||||
|
||||
pub type KeyListener<V> = Box<
|
||||
dyn Fn(
|
||||
&mut V,
|
||||
&dyn Any,
|
||||
&[&KeyContext],
|
||||
DispatchPhase,
|
||||
&mut ViewContext<V>,
|
||||
) -> Option<Box<dyn Action>>
|
||||
+ 'static,
|
||||
>;
|
||||
pub(crate) type KeyDownListener<V> =
|
||||
Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
|
||||
|
||||
pub(crate) type KeyUpListener<V> =
|
||||
Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
|
||||
|
||||
pub type ActionListener<V> =
|
||||
Box<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Event: 'static>(
|
||||
&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::<Event>() {
|
||||
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<Pixels> {
|
||||
self.window.mouse_position
|
||||
@@ -1955,6 +1991,32 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn on_key_event<Event: 'static>(
|
||||
&mut self,
|
||||
handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + '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<V>) + '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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user