Merge branch 'main' into project-panel2
This commit is contained in:
commit
2eedd2ad03
26 changed files with 1687 additions and 1601 deletions
|
@ -41,8 +41,8 @@ use git::diff_hunk_to_display;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext,
|
action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext,
|
||||||
AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
|
AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
|
||||||
DispatchContext, EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight,
|
EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla,
|
||||||
HighlightStyle, Hsla, InputHandler, Model, MouseButton, ParentElement, Pixels, Render,
|
InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render,
|
||||||
StatelessInteractive, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
|
StatelessInteractive, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
|
||||||
ViewContext, VisualContext, WeakView, WindowContext,
|
ViewContext, VisualContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
|
@ -646,7 +646,7 @@ pub struct Editor {
|
||||||
collapse_matches: bool,
|
collapse_matches: bool,
|
||||||
autoindent_mode: Option<AutoindentMode>,
|
autoindent_mode: Option<AutoindentMode>,
|
||||||
workspace: Option<(WeakView<Workspace>, i64)>,
|
workspace: Option<(WeakView<Workspace>, i64)>,
|
||||||
keymap_context_layers: BTreeMap<TypeId, DispatchContext>,
|
keymap_context_layers: BTreeMap<TypeId, KeyContext>,
|
||||||
input_enabled: bool,
|
input_enabled: bool,
|
||||||
read_only: bool,
|
read_only: bool,
|
||||||
leader_peer_id: Option<PeerId>,
|
leader_peer_id: Option<PeerId>,
|
||||||
|
@ -1981,9 +1981,9 @@ impl Editor {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_context(&self, cx: &AppContext) -> DispatchContext {
|
fn dispatch_context(&self, cx: &AppContext) -> KeyContext {
|
||||||
let mut dispatch_context = DispatchContext::default();
|
let mut dispatch_context = KeyContext::default();
|
||||||
dispatch_context.insert("Editor");
|
dispatch_context.add("Editor");
|
||||||
let mode = match self.mode {
|
let mode = match self.mode {
|
||||||
EditorMode::SingleLine => "single_line",
|
EditorMode::SingleLine => "single_line",
|
||||||
EditorMode::AutoHeight { .. } => "auto_height",
|
EditorMode::AutoHeight { .. } => "auto_height",
|
||||||
|
@ -1991,17 +1991,17 @@ impl Editor {
|
||||||
};
|
};
|
||||||
dispatch_context.set("mode", mode);
|
dispatch_context.set("mode", mode);
|
||||||
if self.pending_rename.is_some() {
|
if self.pending_rename.is_some() {
|
||||||
dispatch_context.insert("renaming");
|
dispatch_context.add("renaming");
|
||||||
}
|
}
|
||||||
if self.context_menu_visible() {
|
if self.context_menu_visible() {
|
||||||
match self.context_menu.read().as_ref() {
|
match self.context_menu.read().as_ref() {
|
||||||
Some(ContextMenu::Completions(_)) => {
|
Some(ContextMenu::Completions(_)) => {
|
||||||
dispatch_context.insert("menu");
|
dispatch_context.add("menu");
|
||||||
dispatch_context.insert("showing_completions")
|
dispatch_context.add("showing_completions")
|
||||||
}
|
}
|
||||||
Some(ContextMenu::CodeActions(_)) => {
|
Some(ContextMenu::CodeActions(_)) => {
|
||||||
dispatch_context.insert("menu");
|
dispatch_context.add("menu");
|
||||||
dispatch_context.insert("showing_code_actions")
|
dispatch_context.add("showing_code_actions")
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,11 @@ use anyhow::Result;
|
||||||
use collections::{BTreeMap, HashMap};
|
use collections::{BTreeMap, HashMap};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
|
black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
|
||||||
BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase,
|
BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element,
|
||||||
Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla,
|
ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler,
|
||||||
InputHandler, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton,
|
KeyContext, KeyDownEvent, KeyMatch, Line, LineLayout, Modifiers, MouseButton, MouseDownEvent,
|
||||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size,
|
MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size, Style, TextRun,
|
||||||
Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout,
|
TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::language_settings::ShowWhitespaceSetting;
|
use language::language_settings::ShowWhitespaceSetting;
|
||||||
|
@ -2456,11 +2456,170 @@ impl Element<Editor> for EditorElement {
|
||||||
|
|
||||||
let dispatch_context = editor.dispatch_context(cx);
|
let dispatch_context = editor.dispatch_context(cx);
|
||||||
cx.with_element_id(cx.view().entity_id(), |global_id, cx| {
|
cx.with_element_id(cx.view().entity_id(), |global_id, cx| {
|
||||||
cx.with_key_dispatch_context(dispatch_context, |cx| {
|
cx.with_key_dispatch(
|
||||||
cx.with_key_listeners(build_key_listeners(global_id), |cx| {
|
dispatch_context,
|
||||||
cx.with_focus(editor.focus_handle.clone(), |_| {})
|
Some(editor.focus_handle.clone()),
|
||||||
});
|
|_, cx| {
|
||||||
})
|
register_action(cx, Editor::move_left);
|
||||||
|
register_action(cx, Editor::move_right);
|
||||||
|
register_action(cx, Editor::move_down);
|
||||||
|
register_action(cx, Editor::move_up);
|
||||||
|
// on_action(cx, Editor::new_file); todo!()
|
||||||
|
// on_action(cx, Editor::new_file_in_direction); todo!()
|
||||||
|
register_action(cx, Editor::cancel);
|
||||||
|
register_action(cx, Editor::newline);
|
||||||
|
register_action(cx, Editor::newline_above);
|
||||||
|
register_action(cx, Editor::newline_below);
|
||||||
|
register_action(cx, Editor::backspace);
|
||||||
|
register_action(cx, Editor::delete);
|
||||||
|
register_action(cx, Editor::tab);
|
||||||
|
register_action(cx, Editor::tab_prev);
|
||||||
|
register_action(cx, Editor::indent);
|
||||||
|
register_action(cx, Editor::outdent);
|
||||||
|
register_action(cx, Editor::delete_line);
|
||||||
|
register_action(cx, Editor::join_lines);
|
||||||
|
register_action(cx, Editor::sort_lines_case_sensitive);
|
||||||
|
register_action(cx, Editor::sort_lines_case_insensitive);
|
||||||
|
register_action(cx, Editor::reverse_lines);
|
||||||
|
register_action(cx, Editor::shuffle_lines);
|
||||||
|
register_action(cx, Editor::convert_to_upper_case);
|
||||||
|
register_action(cx, Editor::convert_to_lower_case);
|
||||||
|
register_action(cx, Editor::convert_to_title_case);
|
||||||
|
register_action(cx, Editor::convert_to_snake_case);
|
||||||
|
register_action(cx, Editor::convert_to_kebab_case);
|
||||||
|
register_action(cx, Editor::convert_to_upper_camel_case);
|
||||||
|
register_action(cx, Editor::convert_to_lower_camel_case);
|
||||||
|
register_action(cx, Editor::delete_to_previous_word_start);
|
||||||
|
register_action(cx, Editor::delete_to_previous_subword_start);
|
||||||
|
register_action(cx, Editor::delete_to_next_word_end);
|
||||||
|
register_action(cx, Editor::delete_to_next_subword_end);
|
||||||
|
register_action(cx, Editor::delete_to_beginning_of_line);
|
||||||
|
register_action(cx, Editor::delete_to_end_of_line);
|
||||||
|
register_action(cx, Editor::cut_to_end_of_line);
|
||||||
|
register_action(cx, Editor::duplicate_line);
|
||||||
|
register_action(cx, Editor::move_line_up);
|
||||||
|
register_action(cx, Editor::move_line_down);
|
||||||
|
register_action(cx, Editor::transpose);
|
||||||
|
register_action(cx, Editor::cut);
|
||||||
|
register_action(cx, Editor::copy);
|
||||||
|
register_action(cx, Editor::paste);
|
||||||
|
register_action(cx, Editor::undo);
|
||||||
|
register_action(cx, Editor::redo);
|
||||||
|
register_action(cx, Editor::move_page_up);
|
||||||
|
register_action(cx, Editor::move_page_down);
|
||||||
|
register_action(cx, Editor::next_screen);
|
||||||
|
register_action(cx, Editor::scroll_cursor_top);
|
||||||
|
register_action(cx, Editor::scroll_cursor_center);
|
||||||
|
register_action(cx, Editor::scroll_cursor_bottom);
|
||||||
|
register_action(cx, |editor, _: &LineDown, cx| {
|
||||||
|
editor.scroll_screen(&ScrollAmount::Line(1.), cx)
|
||||||
|
});
|
||||||
|
register_action(cx, |editor, _: &LineUp, cx| {
|
||||||
|
editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
|
||||||
|
});
|
||||||
|
register_action(cx, |editor, _: &HalfPageDown, cx| {
|
||||||
|
editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
|
||||||
|
});
|
||||||
|
register_action(cx, |editor, _: &HalfPageUp, cx| {
|
||||||
|
editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
|
||||||
|
});
|
||||||
|
register_action(cx, |editor, _: &PageDown, cx| {
|
||||||
|
editor.scroll_screen(&ScrollAmount::Page(1.), cx)
|
||||||
|
});
|
||||||
|
register_action(cx, |editor, _: &PageUp, cx| {
|
||||||
|
editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
|
||||||
|
});
|
||||||
|
register_action(cx, Editor::move_to_previous_word_start);
|
||||||
|
register_action(cx, Editor::move_to_previous_subword_start);
|
||||||
|
register_action(cx, Editor::move_to_next_word_end);
|
||||||
|
register_action(cx, Editor::move_to_next_subword_end);
|
||||||
|
register_action(cx, Editor::move_to_beginning_of_line);
|
||||||
|
register_action(cx, Editor::move_to_end_of_line);
|
||||||
|
register_action(cx, Editor::move_to_start_of_paragraph);
|
||||||
|
register_action(cx, Editor::move_to_end_of_paragraph);
|
||||||
|
register_action(cx, Editor::move_to_beginning);
|
||||||
|
register_action(cx, Editor::move_to_end);
|
||||||
|
register_action(cx, Editor::select_up);
|
||||||
|
register_action(cx, Editor::select_down);
|
||||||
|
register_action(cx, Editor::select_left);
|
||||||
|
register_action(cx, Editor::select_right);
|
||||||
|
register_action(cx, Editor::select_to_previous_word_start);
|
||||||
|
register_action(cx, Editor::select_to_previous_subword_start);
|
||||||
|
register_action(cx, Editor::select_to_next_word_end);
|
||||||
|
register_action(cx, Editor::select_to_next_subword_end);
|
||||||
|
register_action(cx, Editor::select_to_beginning_of_line);
|
||||||
|
register_action(cx, Editor::select_to_end_of_line);
|
||||||
|
register_action(cx, Editor::select_to_start_of_paragraph);
|
||||||
|
register_action(cx, Editor::select_to_end_of_paragraph);
|
||||||
|
register_action(cx, Editor::select_to_beginning);
|
||||||
|
register_action(cx, Editor::select_to_end);
|
||||||
|
register_action(cx, Editor::select_all);
|
||||||
|
register_action(cx, |editor, action, cx| {
|
||||||
|
editor.select_all_matches(action, cx).log_err();
|
||||||
|
});
|
||||||
|
register_action(cx, Editor::select_line);
|
||||||
|
register_action(cx, Editor::split_selection_into_lines);
|
||||||
|
register_action(cx, Editor::add_selection_above);
|
||||||
|
register_action(cx, Editor::add_selection_below);
|
||||||
|
register_action(cx, |editor, action, cx| {
|
||||||
|
editor.select_next(action, cx).log_err();
|
||||||
|
});
|
||||||
|
register_action(cx, |editor, action, cx| {
|
||||||
|
editor.select_previous(action, cx).log_err();
|
||||||
|
});
|
||||||
|
register_action(cx, Editor::toggle_comments);
|
||||||
|
register_action(cx, Editor::select_larger_syntax_node);
|
||||||
|
register_action(cx, Editor::select_smaller_syntax_node);
|
||||||
|
register_action(cx, Editor::move_to_enclosing_bracket);
|
||||||
|
register_action(cx, Editor::undo_selection);
|
||||||
|
register_action(cx, Editor::redo_selection);
|
||||||
|
register_action(cx, Editor::go_to_diagnostic);
|
||||||
|
register_action(cx, Editor::go_to_prev_diagnostic);
|
||||||
|
register_action(cx, Editor::go_to_hunk);
|
||||||
|
register_action(cx, Editor::go_to_prev_hunk);
|
||||||
|
register_action(cx, Editor::go_to_definition);
|
||||||
|
register_action(cx, Editor::go_to_definition_split);
|
||||||
|
register_action(cx, Editor::go_to_type_definition);
|
||||||
|
register_action(cx, Editor::go_to_type_definition_split);
|
||||||
|
register_action(cx, Editor::fold);
|
||||||
|
register_action(cx, Editor::fold_at);
|
||||||
|
register_action(cx, Editor::unfold_lines);
|
||||||
|
register_action(cx, Editor::unfold_at);
|
||||||
|
register_action(cx, Editor::fold_selected_ranges);
|
||||||
|
register_action(cx, Editor::show_completions);
|
||||||
|
register_action(cx, Editor::toggle_code_actions);
|
||||||
|
// on_action(cx, Editor::open_excerpts); todo!()
|
||||||
|
register_action(cx, Editor::toggle_soft_wrap);
|
||||||
|
register_action(cx, Editor::toggle_inlay_hints);
|
||||||
|
register_action(cx, Editor::reveal_in_finder);
|
||||||
|
register_action(cx, Editor::copy_path);
|
||||||
|
register_action(cx, Editor::copy_relative_path);
|
||||||
|
register_action(cx, Editor::copy_highlight_json);
|
||||||
|
register_action(cx, |editor, action, cx| {
|
||||||
|
editor
|
||||||
|
.format(action, cx)
|
||||||
|
.map(|task| task.detach_and_log_err(cx));
|
||||||
|
});
|
||||||
|
register_action(cx, Editor::restart_language_server);
|
||||||
|
register_action(cx, Editor::show_character_palette);
|
||||||
|
// on_action(cx, Editor::confirm_completion); todo!()
|
||||||
|
register_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!()
|
||||||
|
register_action(cx, Editor::next_copilot_suggestion);
|
||||||
|
register_action(cx, Editor::previous_copilot_suggestion);
|
||||||
|
register_action(cx, Editor::copilot_suggest);
|
||||||
|
register_action(cx, Editor::context_menu_first);
|
||||||
|
register_action(cx, Editor::context_menu_prev);
|
||||||
|
register_action(cx, Editor::context_menu_next);
|
||||||
|
register_action(cx, Editor::context_menu_last);
|
||||||
|
},
|
||||||
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3995,212 +4154,14 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fn build_key_listeners(
|
fn register_action<T: Action>(
|
||||||
global_element_id: GlobalElementId,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> 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),
|
|
||||||
build_key_listener(
|
|
||||||
move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
|
|
||||||
if phase == DispatchPhase::Bubble {
|
|
||||||
if let KeyMatch::Some(action) = cx.match_keystroke(
|
|
||||||
&global_element_id,
|
|
||||||
&key_down.keystroke,
|
|
||||||
dispatch_context,
|
|
||||||
) {
|
|
||||||
return Some(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_key_listener<T: 'static>(
|
|
||||||
listener: impl Fn(
|
|
||||||
&mut Editor,
|
|
||||||
&T,
|
|
||||||
&[&DispatchContext],
|
|
||||||
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>(
|
|
||||||
listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
|
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 {
|
if phase == DispatchPhase::Bubble {
|
||||||
listener(editor, action, cx);
|
listener(editor, action, cx);
|
||||||
}
|
}
|
||||||
None
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
|
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, AppContext, Div, EventEmitter, ParentElement, Render, SharedString,
|
actions, div, AppContext, Div, EventEmitter, ParentElement, Render, SharedString,
|
||||||
StatefulInteractivity, StatelessInteractive, Styled, Subscription, View, ViewContext,
|
StatelessInteractive, Styled, Subscription, View, ViewContext, VisualContext, WindowContext,
|
||||||
VisualContext, WindowContext,
|
|
||||||
};
|
};
|
||||||
use text::{Bias, Point};
|
use text::{Bias, Point};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
@ -146,11 +145,11 @@ impl GoToLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for GoToLine {
|
impl Render for GoToLine {
|
||||||
type Element = Div<Self, StatefulInteractivity<Self>>;
|
type Element = Div<Self>;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
modal(cx)
|
modal(cx)
|
||||||
.id("go to line")
|
.context("GoToLine")
|
||||||
.on_action(Self::cancel)
|
.on_action(Self::cancel)
|
||||||
.on_action(Self::confirm)
|
.on_action(Self::confirm)
|
||||||
.w_96()
|
.w_96()
|
||||||
|
|
1
crates/gpui/src/dispatch.rs
Normal file
1
crates/gpui/src/dispatch.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::SharedString;
|
use crate::SharedString;
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::HashMap;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
|
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -186,401 +186,3 @@ macro_rules! actions {
|
||||||
actions!($($rest)*);
|
actions!($($rest)*);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
||||||
pub struct DispatchContext {
|
|
||||||
set: HashSet<SharedString>,
|
|
||||||
map: HashMap<SharedString, SharedString>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for DispatchContext {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(value: &'a str) -> Result<Self> {
|
|
||||||
Self::parse(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DispatchContext {
|
|
||||||
pub fn parse(source: &str) -> Result<Self> {
|
|
||||||
let mut context = Self::default();
|
|
||||||
let source = skip_whitespace(source);
|
|
||||||
Self::parse_expr(&source, &mut context)?;
|
|
||||||
Ok(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
|
|
||||||
if source.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let key = source
|
|
||||||
.chars()
|
|
||||||
.take_while(|c| is_identifier_char(*c))
|
|
||||||
.collect::<String>();
|
|
||||||
source = skip_whitespace(&source[key.len()..]);
|
|
||||||
if let Some(suffix) = source.strip_prefix('=') {
|
|
||||||
source = skip_whitespace(suffix);
|
|
||||||
let value = source
|
|
||||||
.chars()
|
|
||||||
.take_while(|c| is_identifier_char(*c))
|
|
||||||
.collect::<String>();
|
|
||||||
source = skip_whitespace(&source[value.len()..]);
|
|
||||||
context.set(key, value);
|
|
||||||
} else {
|
|
||||||
context.insert(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::parse_expr(source, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.set.is_empty() && self.map.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.set.clear();
|
|
||||||
self.map.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extend(&mut self, other: &Self) {
|
|
||||||
for v in &other.set {
|
|
||||||
self.set.insert(v.clone());
|
|
||||||
}
|
|
||||||
for (k, v) in &other.map {
|
|
||||||
self.map.insert(k.clone(), v.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert<I: Into<SharedString>>(&mut self, identifier: I) {
|
|
||||||
self.set.insert(identifier.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
|
|
||||||
self.map.insert(key.into(), value.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
|
||||||
pub enum DispatchContextPredicate {
|
|
||||||
Identifier(SharedString),
|
|
||||||
Equal(SharedString, SharedString),
|
|
||||||
NotEqual(SharedString, SharedString),
|
|
||||||
Child(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
|
|
||||||
Not(Box<DispatchContextPredicate>),
|
|
||||||
And(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
|
|
||||||
Or(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DispatchContextPredicate {
|
|
||||||
pub fn parse(source: &str) -> Result<Self> {
|
|
||||||
let source = skip_whitespace(source);
|
|
||||||
let (predicate, rest) = Self::parse_expr(source, 0)?;
|
|
||||||
if let Some(next) = rest.chars().next() {
|
|
||||||
Err(anyhow!("unexpected character {next:?}"))
|
|
||||||
} else {
|
|
||||||
Ok(predicate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval(&self, contexts: &[&DispatchContext]) -> bool {
|
|
||||||
let Some(context) = contexts.last() else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
match self {
|
|
||||||
Self::Identifier(name) => context.set.contains(name),
|
|
||||||
Self::Equal(left, right) => context
|
|
||||||
.map
|
|
||||||
.get(left)
|
|
||||||
.map(|value| value == right)
|
|
||||||
.unwrap_or(false),
|
|
||||||
Self::NotEqual(left, right) => context
|
|
||||||
.map
|
|
||||||
.get(left)
|
|
||||||
.map(|value| value != right)
|
|
||||||
.unwrap_or(true),
|
|
||||||
Self::Not(pred) => !pred.eval(contexts),
|
|
||||||
Self::Child(parent, child) => {
|
|
||||||
parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
|
|
||||||
}
|
|
||||||
Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
|
|
||||||
Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
|
|
||||||
type Op = fn(
|
|
||||||
DispatchContextPredicate,
|
|
||||||
DispatchContextPredicate,
|
|
||||||
) -> Result<DispatchContextPredicate>;
|
|
||||||
|
|
||||||
let (mut predicate, rest) = Self::parse_primary(source)?;
|
|
||||||
source = rest;
|
|
||||||
|
|
||||||
'parse: loop {
|
|
||||||
for (operator, precedence, constructor) in [
|
|
||||||
(">", PRECEDENCE_CHILD, Self::new_child as Op),
|
|
||||||
("&&", PRECEDENCE_AND, Self::new_and as Op),
|
|
||||||
("||", PRECEDENCE_OR, Self::new_or as Op),
|
|
||||||
("==", PRECEDENCE_EQ, Self::new_eq as Op),
|
|
||||||
("!=", PRECEDENCE_EQ, Self::new_neq as Op),
|
|
||||||
] {
|
|
||||||
if source.starts_with(operator) && precedence >= min_precedence {
|
|
||||||
source = skip_whitespace(&source[operator.len()..]);
|
|
||||||
let (right, rest) = Self::parse_expr(source, precedence + 1)?;
|
|
||||||
predicate = constructor(predicate, right)?;
|
|
||||||
source = rest;
|
|
||||||
continue 'parse;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((predicate, source))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
|
|
||||||
let next = source
|
|
||||||
.chars()
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| anyhow!("unexpected eof"))?;
|
|
||||||
match next {
|
|
||||||
'(' => {
|
|
||||||
source = skip_whitespace(&source[1..]);
|
|
||||||
let (predicate, rest) = Self::parse_expr(source, 0)?;
|
|
||||||
if rest.starts_with(')') {
|
|
||||||
source = skip_whitespace(&rest[1..]);
|
|
||||||
Ok((predicate, source))
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("expected a ')'"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'!' => {
|
|
||||||
let source = skip_whitespace(&source[1..]);
|
|
||||||
let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
|
|
||||||
Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
|
|
||||||
}
|
|
||||||
_ if is_identifier_char(next) => {
|
|
||||||
let len = source
|
|
||||||
.find(|c: char| !is_identifier_char(c))
|
|
||||||
.unwrap_or(source.len());
|
|
||||||
let (identifier, rest) = source.split_at(len);
|
|
||||||
source = skip_whitespace(rest);
|
|
||||||
Ok((
|
|
||||||
DispatchContextPredicate::Identifier(identifier.to_string().into()),
|
|
||||||
source,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
_ => Err(anyhow!("unexpected character {next:?}")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_or(self, other: Self) -> Result<Self> {
|
|
||||||
Ok(Self::Or(Box::new(self), Box::new(other)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_and(self, other: Self) -> Result<Self> {
|
|
||||||
Ok(Self::And(Box::new(self), Box::new(other)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_child(self, other: Self) -> Result<Self> {
|
|
||||||
Ok(Self::Child(Box::new(self), Box::new(other)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_eq(self, other: Self) -> Result<Self> {
|
|
||||||
if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
|
|
||||||
Ok(Self::Equal(left, right))
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("operands must be identifiers"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_neq(self, other: Self) -> Result<Self> {
|
|
||||||
if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
|
|
||||||
Ok(Self::NotEqual(left, right))
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("operands must be identifiers"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PRECEDENCE_CHILD: u32 = 1;
|
|
||||||
const PRECEDENCE_OR: u32 = 2;
|
|
||||||
const PRECEDENCE_AND: u32 = 3;
|
|
||||||
const PRECEDENCE_EQ: u32 = 4;
|
|
||||||
const PRECEDENCE_NOT: u32 = 5;
|
|
||||||
|
|
||||||
fn is_identifier_char(c: char) -> bool {
|
|
||||||
c.is_alphanumeric() || c == '_' || c == '-'
|
|
||||||
}
|
|
||||||
|
|
||||||
fn skip_whitespace(source: &str) -> &str {
|
|
||||||
let len = source
|
|
||||||
.find(|c: char| !c.is_whitespace())
|
|
||||||
.unwrap_or(source.len());
|
|
||||||
&source[len..]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate as gpui;
|
|
||||||
use DispatchContextPredicate::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_actions_definition() {
|
|
||||||
{
|
|
||||||
actions!(A, B, C, D, E, F, G);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
actions!(
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
C,
|
|
||||||
D,
|
|
||||||
E,
|
|
||||||
F,
|
|
||||||
G, // Don't wrap, test the trailing comma
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_context() {
|
|
||||||
let mut expected = DispatchContext::default();
|
|
||||||
expected.set("foo", "bar");
|
|
||||||
expected.insert("baz");
|
|
||||||
assert_eq!(DispatchContext::parse("baz foo=bar").unwrap(), expected);
|
|
||||||
assert_eq!(DispatchContext::parse("foo = bar baz").unwrap(), expected);
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContext::parse(" baz foo = bar baz").unwrap(),
|
|
||||||
expected
|
|
||||||
);
|
|
||||||
assert_eq!(DispatchContext::parse(" foo = bar baz").unwrap(), expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_identifiers() {
|
|
||||||
// Identifiers
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse("abc12").unwrap(),
|
|
||||||
Identifier("abc12".into())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse("_1a").unwrap(),
|
|
||||||
Identifier("_1a".into())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_negations() {
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse("!abc").unwrap(),
|
|
||||||
Not(Box::new(Identifier("abc".into())))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse(" ! ! abc").unwrap(),
|
|
||||||
Not(Box::new(Not(Box::new(Identifier("abc".into())))))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_equality_operators() {
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse("a == b").unwrap(),
|
|
||||||
Equal("a".into(), "b".into())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse("c!=d").unwrap(),
|
|
||||||
NotEqual("c".into(), "d".into())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse("c == !d")
|
|
||||||
.unwrap_err()
|
|
||||||
.to_string(),
|
|
||||||
"operands must be identifiers"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_boolean_operators() {
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse("a || b").unwrap(),
|
|
||||||
Or(
|
|
||||||
Box::new(Identifier("a".into())),
|
|
||||||
Box::new(Identifier("b".into()))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse("a || !b && c").unwrap(),
|
|
||||||
Or(
|
|
||||||
Box::new(Identifier("a".into())),
|
|
||||||
Box::new(And(
|
|
||||||
Box::new(Not(Box::new(Identifier("b".into())))),
|
|
||||||
Box::new(Identifier("c".into()))
|
|
||||||
))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse("a && b || c&&d").unwrap(),
|
|
||||||
Or(
|
|
||||||
Box::new(And(
|
|
||||||
Box::new(Identifier("a".into())),
|
|
||||||
Box::new(Identifier("b".into()))
|
|
||||||
)),
|
|
||||||
Box::new(And(
|
|
||||||
Box::new(Identifier("c".into())),
|
|
||||||
Box::new(Identifier("d".into()))
|
|
||||||
))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse("a == b && c || d == e && f").unwrap(),
|
|
||||||
Or(
|
|
||||||
Box::new(And(
|
|
||||||
Box::new(Equal("a".into(), "b".into())),
|
|
||||||
Box::new(Identifier("c".into()))
|
|
||||||
)),
|
|
||||||
Box::new(And(
|
|
||||||
Box::new(Equal("d".into(), "e".into())),
|
|
||||||
Box::new(Identifier("f".into()))
|
|
||||||
))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse("a && b && c && d").unwrap(),
|
|
||||||
And(
|
|
||||||
Box::new(And(
|
|
||||||
Box::new(And(
|
|
||||||
Box::new(Identifier("a".into())),
|
|
||||||
Box::new(Identifier("b".into()))
|
|
||||||
)),
|
|
||||||
Box::new(Identifier("c".into())),
|
|
||||||
)),
|
|
||||||
Box::new(Identifier("d".into()))
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_parenthesized_expressions() {
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse("a && (b == c || d != e)").unwrap(),
|
|
||||||
And(
|
|
||||||
Box::new(Identifier("a".into())),
|
|
||||||
Box::new(Or(
|
|
||||||
Box::new(Equal("b".into(), "c".into())),
|
|
||||||
Box::new(NotEqual("d".into(), "e".into())),
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
DispatchContextPredicate::parse(" ( a || b ) ").unwrap(),
|
|
||||||
Or(
|
|
||||||
Box::new(Identifier("a".into())),
|
|
||||||
Box::new(Identifier("b".into())),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -641,14 +641,19 @@ impl AppContext {
|
||||||
// The window might change focus multiple times in an effect cycle.
|
// The window might change focus multiple times in an effect cycle.
|
||||||
// We only honor effects for the most recently focused handle.
|
// We only honor effects for the most recently focused handle.
|
||||||
if cx.window.focus == focused {
|
if cx.window.focus == focused {
|
||||||
|
// if someone calls focus multiple times in one frame with the same handle
|
||||||
|
// the first apply_focus_changed_effect will have taken the last blur already
|
||||||
|
// and run the rest of this, so we can return.
|
||||||
|
let Some(last_blur) = cx.window.last_blur.take() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let focused = focused
|
let focused = focused
|
||||||
.map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap());
|
.map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap());
|
||||||
let blurred = cx
|
|
||||||
.window
|
let blurred =
|
||||||
.last_blur
|
last_blur.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles));
|
||||||
.take()
|
|
||||||
.unwrap()
|
|
||||||
.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles));
|
|
||||||
let focus_changed = focused.is_some() || blurred.is_some();
|
let focus_changed = focused.is_some() || blurred.is_some();
|
||||||
let event = FocusEvent { focused, blurred };
|
let event = FocusEvent { focused, blurred };
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,33 @@
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId,
|
point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, ElementInteractivity,
|
||||||
ElementInteractivity, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
|
FocusHandle, FocusListeners, Focusable, FocusableKeyDispatch, GlobalElementId, GroupBounds,
|
||||||
GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
|
InteractiveElementState, KeyContext, KeyDispatch, LayoutId, NonFocusableKeyDispatch, Overflow,
|
||||||
Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
|
ParentElement, Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity,
|
||||||
StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext, Visibility,
|
StatelessInteractive, StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext,
|
||||||
|
Visibility,
|
||||||
};
|
};
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
pub struct Div<
|
pub struct Div<
|
||||||
V: 'static,
|
V: 'static,
|
||||||
I: ElementInteractivity<V> = StatelessInteractivity<V>,
|
I: ElementInteractivity<V> = StatelessInteractivity<V>,
|
||||||
F: ElementFocus<V> = FocusDisabled,
|
K: KeyDispatch<V> = NonFocusableKeyDispatch,
|
||||||
> {
|
> {
|
||||||
interactivity: I,
|
interactivity: I,
|
||||||
focus: F,
|
key_dispatch: K,
|
||||||
children: SmallVec<[AnyElement<V>; 2]>,
|
children: SmallVec<[AnyElement<V>; 2]>,
|
||||||
group: Option<SharedString>,
|
group: Option<SharedString>,
|
||||||
base_style: StyleRefinement,
|
base_style: StyleRefinement,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, FocusDisabled> {
|
pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
|
||||||
Div {
|
Div {
|
||||||
interactivity: StatelessInteractivity::default(),
|
interactivity: StatelessInteractivity::default(),
|
||||||
focus: FocusDisabled,
|
key_dispatch: NonFocusableKeyDispatch::default(),
|
||||||
children: SmallVec::new(),
|
children: SmallVec::new(),
|
||||||
group: None,
|
group: None,
|
||||||
base_style: StyleRefinement::default(),
|
base_style: StyleRefinement::default(),
|
||||||
|
@ -33,12 +37,12 @@ pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, FocusDisabled> {
|
||||||
impl<V, F> Div<V, StatelessInteractivity<V>, F>
|
impl<V, F> Div<V, StatelessInteractivity<V>, F>
|
||||||
where
|
where
|
||||||
V: 'static,
|
V: 'static,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteractivity<V>, F> {
|
pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteractivity<V>, F> {
|
||||||
Div {
|
Div {
|
||||||
interactivity: StatefulInteractivity::new(id.into(), self.interactivity),
|
interactivity: StatefulInteractivity::new(id.into(), self.interactivity),
|
||||||
focus: self.focus,
|
key_dispatch: self.key_dispatch,
|
||||||
children: self.children,
|
children: self.children,
|
||||||
group: self.group,
|
group: self.group,
|
||||||
base_style: self.base_style,
|
base_style: self.base_style,
|
||||||
|
@ -49,7 +53,7 @@ where
|
||||||
impl<V, I, F> Div<V, I, F>
|
impl<V, I, F> Div<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
pub fn group(mut self, group: impl Into<SharedString>) -> Self {
|
pub fn group(mut self, group: impl Into<SharedString>) -> Self {
|
||||||
self.group = Some(group.into());
|
self.group = Some(group.into());
|
||||||
|
@ -61,6 +65,18 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn context<C>(mut self, context: C) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
C: TryInto<KeyContext>,
|
||||||
|
C::Error: Debug,
|
||||||
|
{
|
||||||
|
if let Some(context) = context.try_into().log_err() {
|
||||||
|
*self.key_dispatch.key_context_mut() = context;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn overflow_hidden(mut self) -> Self {
|
pub fn overflow_hidden(mut self) -> Self {
|
||||||
self.base_style.overflow.x = Some(Overflow::Hidden);
|
self.base_style.overflow.x = Some(Overflow::Hidden);
|
||||||
self.base_style.overflow.y = Some(Overflow::Hidden);
|
self.base_style.overflow.y = Some(Overflow::Hidden);
|
||||||
|
@ -97,7 +113,7 @@ where
|
||||||
) -> Style {
|
) -> Style {
|
||||||
let mut computed_style = Style::default();
|
let mut computed_style = Style::default();
|
||||||
computed_style.refine(&self.base_style);
|
computed_style.refine(&self.base_style);
|
||||||
self.focus.refine_style(&mut computed_style, cx);
|
self.key_dispatch.refine_style(&mut computed_style, cx);
|
||||||
self.interactivity.refine_style(
|
self.interactivity.refine_style(
|
||||||
&mut computed_style,
|
&mut computed_style,
|
||||||
bounds,
|
bounds,
|
||||||
|
@ -108,11 +124,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
|
impl<V: 'static> Div<V, StatefulInteractivity<V>, NonFocusableKeyDispatch> {
|
||||||
pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
|
pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
|
||||||
Div {
|
Div {
|
||||||
interactivity: self.interactivity,
|
interactivity: self.interactivity,
|
||||||
focus: FocusEnabled::new(),
|
key_dispatch: FocusableKeyDispatch::new(self.key_dispatch),
|
||||||
children: self.children,
|
children: self.children,
|
||||||
group: self.group,
|
group: self.group,
|
||||||
base_style: self.base_style,
|
base_style: self.base_style,
|
||||||
|
@ -122,10 +138,10 @@ impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
|
||||||
pub fn track_focus(
|
pub fn track_focus(
|
||||||
self,
|
self,
|
||||||
handle: &FocusHandle,
|
handle: &FocusHandle,
|
||||||
) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
|
) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
|
||||||
Div {
|
Div {
|
||||||
interactivity: self.interactivity,
|
interactivity: self.interactivity,
|
||||||
focus: FocusEnabled::tracked(handle),
|
key_dispatch: FocusableKeyDispatch::tracked(self.key_dispatch, handle),
|
||||||
children: self.children,
|
children: self.children,
|
||||||
group: self.group,
|
group: self.group,
|
||||||
base_style: self.base_style,
|
base_style: self.base_style,
|
||||||
|
@ -149,14 +165,14 @@ impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> Div<V, StatelessInteractivity<V>, FocusDisabled> {
|
impl<V: 'static> Div<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
|
||||||
pub fn track_focus(
|
pub fn track_focus(
|
||||||
self,
|
self,
|
||||||
handle: &FocusHandle,
|
handle: &FocusHandle,
|
||||||
) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
|
) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
|
||||||
Div {
|
Div {
|
||||||
interactivity: self.interactivity.into_stateful(handle),
|
interactivity: self.interactivity.into_stateful(handle),
|
||||||
focus: handle.clone().into(),
|
key_dispatch: FocusableKeyDispatch::tracked(self.key_dispatch, handle),
|
||||||
children: self.children,
|
children: self.children,
|
||||||
group: self.group,
|
group: self.group,
|
||||||
base_style: self.base_style,
|
base_style: self.base_style,
|
||||||
|
@ -164,25 +180,25 @@ impl<V: 'static> Div<V, StatelessInteractivity<V>, FocusDisabled> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V, I> Focusable<V> for Div<V, I, FocusEnabled<V>>
|
impl<V, I> Focusable<V> for Div<V, I, FocusableKeyDispatch<V>>
|
||||||
where
|
where
|
||||||
V: 'static,
|
V: 'static,
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
{
|
{
|
||||||
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
|
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
|
||||||
&mut self.focus.focus_listeners
|
&mut self.key_dispatch.focus_listeners
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_focus_style(&mut self, style: StyleRefinement) {
|
fn set_focus_style(&mut self, style: StyleRefinement) {
|
||||||
self.focus.focus_style = style;
|
self.key_dispatch.focus_style = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_focus_in_style(&mut self, style: StyleRefinement) {
|
fn set_focus_in_style(&mut self, style: StyleRefinement) {
|
||||||
self.focus.focus_in_style = style;
|
self.key_dispatch.focus_in_style = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_in_focus_style(&mut self, style: StyleRefinement) {
|
fn set_in_focus_style(&mut self, style: StyleRefinement) {
|
||||||
self.focus.in_focus_style = style;
|
self.key_dispatch.in_focus_style = style;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +212,7 @@ pub struct DivState {
|
||||||
impl<V, I, F> Element<V> for Div<V, I, F>
|
impl<V, I, F> Element<V> for Div<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
type ElementState = DivState;
|
type ElementState = DivState;
|
||||||
|
|
||||||
|
@ -213,14 +229,18 @@ where
|
||||||
cx: &mut ViewContext<V>,
|
cx: &mut ViewContext<V>,
|
||||||
) -> Self::ElementState {
|
) -> Self::ElementState {
|
||||||
let mut element_state = element_state.unwrap_or_default();
|
let mut element_state = element_state.unwrap_or_default();
|
||||||
self.interactivity.initialize(cx, |cx| {
|
self.with_element_id(cx, |this, _global_id, cx| {
|
||||||
self.focus
|
this.key_dispatch.initialize(
|
||||||
.initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
|
element_state.focus_handle.take(),
|
||||||
|
cx,
|
||||||
|
|focus_handle, cx| {
|
||||||
|
this.interactivity.initialize(cx);
|
||||||
element_state.focus_handle = focus_handle;
|
element_state.focus_handle = focus_handle;
|
||||||
for child in &mut self.children {
|
for child in &mut this.children {
|
||||||
child.initialize(view_state, cx);
|
child.initialize(view_state, cx);
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
element_state
|
element_state
|
||||||
}
|
}
|
||||||
|
@ -288,7 +308,7 @@ where
|
||||||
cx.with_z_index(z_index, |cx| {
|
cx.with_z_index(z_index, |cx| {
|
||||||
cx.with_z_index(0, |cx| {
|
cx.with_z_index(0, |cx| {
|
||||||
style.paint(bounds, cx);
|
style.paint(bounds, cx);
|
||||||
this.focus.paint(bounds, cx);
|
this.key_dispatch.paint(bounds, cx);
|
||||||
this.interactivity.paint(
|
this.interactivity.paint(
|
||||||
bounds,
|
bounds,
|
||||||
content_size,
|
content_size,
|
||||||
|
@ -321,7 +341,7 @@ where
|
||||||
impl<V, I, F> Component<V> for Div<V, I, F>
|
impl<V, I, F> Component<V> for Div<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn render(self) -> AnyElement<V> {
|
fn render(self) -> AnyElement<V> {
|
||||||
AnyElement::new(self)
|
AnyElement::new(self)
|
||||||
|
@ -331,7 +351,7 @@ where
|
||||||
impl<V, I, F> ParentElement<V> for Div<V, I, F>
|
impl<V, I, F> ParentElement<V> for Div<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
||||||
&mut self.children
|
&mut self.children
|
||||||
|
@ -341,7 +361,7 @@ where
|
||||||
impl<V, I, F> Styled for Div<V, I, F>
|
impl<V, I, F> Styled for Div<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn style(&mut self) -> &mut StyleRefinement {
|
fn style(&mut self) -> &mut StyleRefinement {
|
||||||
&mut self.base_style
|
&mut self.base_style
|
||||||
|
@ -351,7 +371,7 @@ where
|
||||||
impl<V, I, F> StatelessInteractive<V> for Div<V, I, F>
|
impl<V, I, F> StatelessInteractive<V> for Div<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
|
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
|
||||||
self.interactivity.as_stateless_mut()
|
self.interactivity.as_stateless_mut()
|
||||||
|
@ -360,7 +380,7 @@ where
|
||||||
|
|
||||||
impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteractivity<V>, F>
|
impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteractivity<V>, F>
|
||||||
where
|
where
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
|
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
|
||||||
&mut self.interactivity
|
&mut self.interactivity
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus,
|
div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementId,
|
||||||
ElementId, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
|
ElementInteractivity, FocusListeners, Focusable, FocusableKeyDispatch, KeyDispatch, LayoutId,
|
||||||
LayoutId, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
|
NonFocusableKeyDispatch, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
|
||||||
StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
|
StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
|
||||||
};
|
};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
@ -10,14 +10,14 @@ use util::ResultExt;
|
||||||
pub struct Img<
|
pub struct Img<
|
||||||
V: 'static,
|
V: 'static,
|
||||||
I: ElementInteractivity<V> = StatelessInteractivity<V>,
|
I: ElementInteractivity<V> = StatelessInteractivity<V>,
|
||||||
F: ElementFocus<V> = FocusDisabled,
|
F: KeyDispatch<V> = NonFocusableKeyDispatch,
|
||||||
> {
|
> {
|
||||||
base: Div<V, I, F>,
|
base: Div<V, I, F>,
|
||||||
uri: Option<SharedString>,
|
uri: Option<SharedString>,
|
||||||
grayscale: bool,
|
grayscale: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, FocusDisabled> {
|
pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
|
||||||
Img {
|
Img {
|
||||||
base: div(),
|
base: div(),
|
||||||
uri: None,
|
uri: None,
|
||||||
|
@ -29,7 +29,7 @@ impl<V, I, F> Img<V, I, F>
|
||||||
where
|
where
|
||||||
V: 'static,
|
V: 'static,
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
|
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
|
||||||
self.uri = Some(uri.into());
|
self.uri = Some(uri.into());
|
||||||
|
@ -44,7 +44,7 @@ where
|
||||||
|
|
||||||
impl<V, F> Img<V, StatelessInteractivity<V>, F>
|
impl<V, F> Img<V, StatelessInteractivity<V>, F>
|
||||||
where
|
where
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteractivity<V>, F> {
|
pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteractivity<V>, F> {
|
||||||
Img {
|
Img {
|
||||||
|
@ -58,7 +58,7 @@ where
|
||||||
impl<V, I, F> Component<V> for Img<V, I, F>
|
impl<V, I, F> Component<V> for Img<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn render(self) -> AnyElement<V> {
|
fn render(self) -> AnyElement<V> {
|
||||||
AnyElement::new(self)
|
AnyElement::new(self)
|
||||||
|
@ -68,7 +68,7 @@ where
|
||||||
impl<V, I, F> Element<V> for Img<V, I, F>
|
impl<V, I, F> Element<V> for Img<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
type ElementState = DivState;
|
type ElementState = DivState;
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ where
|
||||||
impl<V, I, F> Styled for Img<V, I, F>
|
impl<V, I, F> Styled for Img<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn style(&mut self) -> &mut StyleRefinement {
|
fn style(&mut self) -> &mut StyleRefinement {
|
||||||
self.base.style()
|
self.base.style()
|
||||||
|
@ -147,7 +147,7 @@ where
|
||||||
impl<V, I, F> StatelessInteractive<V> for Img<V, I, F>
|
impl<V, I, F> StatelessInteractive<V> for Img<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
|
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
|
||||||
self.base.stateless_interactivity()
|
self.base.stateless_interactivity()
|
||||||
|
@ -156,14 +156,14 @@ where
|
||||||
|
|
||||||
impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteractivity<V>, F>
|
impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteractivity<V>, F>
|
||||||
where
|
where
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
|
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
|
||||||
self.base.stateful_interactivity()
|
self.base.stateful_interactivity()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V, I> Focusable<V> for Img<V, I, FocusEnabled<V>>
|
impl<V, I> Focusable<V> for Img<V, I, FocusableKeyDispatch<V>>
|
||||||
where
|
where
|
||||||
V: 'static,
|
V: 'static,
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId,
|
div, AnyElement, Bounds, Component, Div, DivState, Element, ElementId, ElementInteractivity,
|
||||||
ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
|
FocusListeners, Focusable, FocusableKeyDispatch, KeyDispatch, LayoutId,
|
||||||
SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
|
NonFocusableKeyDispatch, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
|
||||||
StatelessInteractivity, StyleRefinement, Styled, ViewContext,
|
StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
pub struct Svg<
|
pub struct Svg<
|
||||||
V: 'static,
|
V: 'static,
|
||||||
I: ElementInteractivity<V> = StatelessInteractivity<V>,
|
I: ElementInteractivity<V> = StatelessInteractivity<V>,
|
||||||
F: ElementFocus<V> = FocusDisabled,
|
F: KeyDispatch<V> = NonFocusableKeyDispatch,
|
||||||
> {
|
> {
|
||||||
base: Div<V, I, F>,
|
base: Div<V, I, F>,
|
||||||
path: Option<SharedString>,
|
path: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
|
pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
|
||||||
Svg {
|
Svg {
|
||||||
base: div(),
|
base: div(),
|
||||||
path: None,
|
path: None,
|
||||||
|
@ -25,7 +25,7 @@ pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
|
||||||
impl<V, I, F> Svg<V, I, F>
|
impl<V, I, F> Svg<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
|
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
|
||||||
self.path = Some(path.into());
|
self.path = Some(path.into());
|
||||||
|
@ -35,7 +35,7 @@ where
|
||||||
|
|
||||||
impl<V, F> Svg<V, StatelessInteractivity<V>, F>
|
impl<V, F> Svg<V, StatelessInteractivity<V>, F>
|
||||||
where
|
where
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteractivity<V>, F> {
|
pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteractivity<V>, F> {
|
||||||
Svg {
|
Svg {
|
||||||
|
@ -48,7 +48,7 @@ where
|
||||||
impl<V, I, F> Component<V> for Svg<V, I, F>
|
impl<V, I, F> Component<V> for Svg<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn render(self) -> AnyElement<V> {
|
fn render(self) -> AnyElement<V> {
|
||||||
AnyElement::new(self)
|
AnyElement::new(self)
|
||||||
|
@ -58,7 +58,7 @@ where
|
||||||
impl<V, I, F> Element<V> for Svg<V, I, F>
|
impl<V, I, F> Element<V> for Svg<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
type ElementState = DivState;
|
type ElementState = DivState;
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ where
|
||||||
impl<V, I, F> Styled for Svg<V, I, F>
|
impl<V, I, F> Styled for Svg<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn style(&mut self) -> &mut StyleRefinement {
|
fn style(&mut self) -> &mut StyleRefinement {
|
||||||
self.base.style()
|
self.base.style()
|
||||||
|
@ -118,7 +118,7 @@ where
|
||||||
impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F>
|
impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
|
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
|
||||||
self.base.stateless_interactivity()
|
self.base.stateless_interactivity()
|
||||||
|
@ -128,14 +128,14 @@ where
|
||||||
impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteractivity<V>, F>
|
impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteractivity<V>, F>
|
||||||
where
|
where
|
||||||
V: 'static,
|
V: 'static,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
|
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
|
||||||
self.base.stateful_interactivity()
|
self.base.stateful_interactivity()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusEnabled<V>>
|
impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusableKeyDispatch<V>>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,252 +0,0 @@
|
||||||
use crate::{
|
|
||||||
Bounds, DispatchPhase, Element, FocusEvent, FocusHandle, MouseDownEvent, Pixels, Style,
|
|
||||||
StyleRefinement, ViewContext, WindowContext,
|
|
||||||
};
|
|
||||||
use refineable::Refineable;
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
|
|
||||||
pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
|
|
||||||
|
|
||||||
pub type FocusListener<V> =
|
|
||||||
Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
|
|
||||||
|
|
||||||
pub trait Focusable<V: 'static>: Element<V> {
|
|
||||||
fn focus_listeners(&mut self) -> &mut FocusListeners<V>;
|
|
||||||
fn set_focus_style(&mut self, style: StyleRefinement);
|
|
||||||
fn set_focus_in_style(&mut self, style: StyleRefinement);
|
|
||||||
fn set_in_focus_style(&mut self, style: StyleRefinement);
|
|
||||||
|
|
||||||
fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.set_focus_style(f(StyleRefinement::default()));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_in(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.set_focus_in_style(f(StyleRefinement::default()));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.set_in_focus_style(f(StyleRefinement::default()));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_focus(
|
|
||||||
mut self,
|
|
||||||
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.focus_listeners()
|
|
||||||
.push(Box::new(move |view, focus_handle, event, cx| {
|
|
||||||
if event.focused.as_ref() == Some(focus_handle) {
|
|
||||||
listener(view, event, cx)
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_blur(
|
|
||||||
mut self,
|
|
||||||
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.focus_listeners()
|
|
||||||
.push(Box::new(move |view, focus_handle, event, cx| {
|
|
||||||
if event.blurred.as_ref() == Some(focus_handle) {
|
|
||||||
listener(view, event, cx)
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_focus_in(
|
|
||||||
mut self,
|
|
||||||
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.focus_listeners()
|
|
||||||
.push(Box::new(move |view, focus_handle, event, cx| {
|
|
||||||
let descendant_blurred = event
|
|
||||||
.blurred
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |blurred| focus_handle.contains(blurred, cx));
|
|
||||||
let descendant_focused = event
|
|
||||||
.focused
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |focused| focus_handle.contains(focused, cx));
|
|
||||||
|
|
||||||
if !descendant_blurred && descendant_focused {
|
|
||||||
listener(view, event, cx)
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_focus_out(
|
|
||||||
mut self,
|
|
||||||
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.focus_listeners()
|
|
||||||
.push(Box::new(move |view, focus_handle, event, cx| {
|
|
||||||
let descendant_blurred = event
|
|
||||||
.blurred
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |blurred| focus_handle.contains(blurred, cx));
|
|
||||||
let descendant_focused = event
|
|
||||||
.focused
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |focused| focus_handle.contains(focused, cx));
|
|
||||||
if descendant_blurred && !descendant_focused {
|
|
||||||
listener(view, event, cx)
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ElementFocus<V: 'static>: 'static {
|
|
||||||
fn as_focusable(&self) -> Option<&FocusEnabled<V>>;
|
|
||||||
fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>>;
|
|
||||||
|
|
||||||
fn initialize<R>(
|
|
||||||
&mut self,
|
|
||||||
focus_handle: Option<FocusHandle>,
|
|
||||||
cx: &mut ViewContext<V>,
|
|
||||||
f: impl FnOnce(Option<FocusHandle>, &mut ViewContext<V>) -> R,
|
|
||||||
) -> R {
|
|
||||||
if let Some(focusable) = self.as_focusable_mut() {
|
|
||||||
let focus_handle = focusable
|
|
||||||
.focus_handle
|
|
||||||
.get_or_insert_with(|| focus_handle.unwrap_or_else(|| cx.focus_handle()))
|
|
||||||
.clone();
|
|
||||||
for listener in focusable.focus_listeners.drain(..) {
|
|
||||||
let focus_handle = focus_handle.clone();
|
|
||||||
cx.on_focus_changed(move |view, event, cx| {
|
|
||||||
listener(view, &focus_handle, event, cx)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
cx.with_focus(focus_handle.clone(), |cx| f(Some(focus_handle), cx))
|
|
||||||
} else {
|
|
||||||
f(None, cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn refine_style(&self, style: &mut Style, cx: &WindowContext) {
|
|
||||||
if let Some(focusable) = self.as_focusable() {
|
|
||||||
let focus_handle = focusable
|
|
||||||
.focus_handle
|
|
||||||
.as_ref()
|
|
||||||
.expect("must call initialize before refine_style");
|
|
||||||
if focus_handle.contains_focused(cx) {
|
|
||||||
style.refine(&focusable.focus_in_style);
|
|
||||||
}
|
|
||||||
|
|
||||||
if focus_handle.within_focused(cx) {
|
|
||||||
style.refine(&focusable.in_focus_style);
|
|
||||||
}
|
|
||||||
|
|
||||||
if focus_handle.is_focused(cx) {
|
|
||||||
style.refine(&focusable.focus_style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
|
|
||||||
if let Some(focusable) = self.as_focusable() {
|
|
||||||
let focus_handle = focusable
|
|
||||||
.focus_handle
|
|
||||||
.clone()
|
|
||||||
.expect("must call initialize before paint");
|
|
||||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
|
||||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
|
||||||
if !cx.default_prevented() {
|
|
||||||
cx.focus(&focus_handle);
|
|
||||||
cx.prevent_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FocusEnabled<V> {
|
|
||||||
pub focus_handle: Option<FocusHandle>,
|
|
||||||
pub focus_listeners: FocusListeners<V>,
|
|
||||||
pub focus_style: StyleRefinement,
|
|
||||||
pub focus_in_style: StyleRefinement,
|
|
||||||
pub in_focus_style: StyleRefinement,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V> FocusEnabled<V> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
focus_handle: None,
|
|
||||||
focus_listeners: FocusListeners::default(),
|
|
||||||
focus_style: StyleRefinement::default(),
|
|
||||||
focus_in_style: StyleRefinement::default(),
|
|
||||||
in_focus_style: StyleRefinement::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tracked(handle: &FocusHandle) -> Self {
|
|
||||||
Self {
|
|
||||||
focus_handle: Some(handle.clone()),
|
|
||||||
focus_listeners: FocusListeners::default(),
|
|
||||||
focus_style: StyleRefinement::default(),
|
|
||||||
focus_in_style: StyleRefinement::default(),
|
|
||||||
in_focus_style: StyleRefinement::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: 'static> ElementFocus<V> for FocusEnabled<V> {
|
|
||||||
fn as_focusable(&self) -> Option<&FocusEnabled<V>> {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>> {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V> From<FocusHandle> for FocusEnabled<V> {
|
|
||||||
fn from(value: FocusHandle) -> Self {
|
|
||||||
Self {
|
|
||||||
focus_handle: Some(value),
|
|
||||||
focus_listeners: FocusListeners::default(),
|
|
||||||
focus_style: StyleRefinement::default(),
|
|
||||||
focus_in_style: StyleRefinement::default(),
|
|
||||||
in_focus_style: StyleRefinement::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FocusDisabled;
|
|
||||||
|
|
||||||
impl<V: 'static> ElementFocus<V> for FocusDisabled {
|
|
||||||
fn as_focusable(&self) -> Option<&FocusEnabled<V>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,13 +6,14 @@ mod color;
|
||||||
mod element;
|
mod element;
|
||||||
mod elements;
|
mod elements;
|
||||||
mod executor;
|
mod executor;
|
||||||
mod focusable;
|
|
||||||
mod geometry;
|
mod geometry;
|
||||||
mod image_cache;
|
mod image_cache;
|
||||||
mod input;
|
mod input;
|
||||||
mod interactive;
|
mod interactive;
|
||||||
|
mod key_dispatch;
|
||||||
mod keymap;
|
mod keymap;
|
||||||
mod platform;
|
mod platform;
|
||||||
|
pub mod prelude;
|
||||||
mod scene;
|
mod scene;
|
||||||
mod style;
|
mod style;
|
||||||
mod styled;
|
mod styled;
|
||||||
|
@ -41,12 +42,12 @@ pub use ctor::ctor;
|
||||||
pub use element::*;
|
pub use element::*;
|
||||||
pub use elements::*;
|
pub use elements::*;
|
||||||
pub use executor::*;
|
pub use executor::*;
|
||||||
pub use focusable::*;
|
|
||||||
pub use geometry::*;
|
pub use geometry::*;
|
||||||
pub use gpui2_macros::*;
|
pub use gpui2_macros::*;
|
||||||
pub use image_cache::*;
|
pub use image_cache::*;
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use interactive::*;
|
pub use interactive::*;
|
||||||
|
pub use key_dispatch::*;
|
||||||
pub use keymap::*;
|
pub use keymap::*;
|
||||||
pub use platform::*;
|
pub use platform::*;
|
||||||
use private::Sealed;
|
use private::Sealed;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds,
|
div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, Bounds, Component,
|
||||||
Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch,
|
DispatchPhase, Div, Element, ElementId, FocusHandle, KeyContext, Keystroke, Modifiers,
|
||||||
Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style,
|
Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, Task, View,
|
||||||
StyleRefinement, Task, View, ViewContext,
|
ViewContext,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
|
@ -164,55 +164,41 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn context<C>(mut self, context: C) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
C: TryInto<DispatchContext>,
|
|
||||||
C::Error: Debug,
|
|
||||||
{
|
|
||||||
self.stateless_interactivity().dispatch_context =
|
|
||||||
context.try_into().expect("invalid dispatch context");
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Capture the given action, fires during the capture phase
|
/// Capture the given action, fires during the capture phase
|
||||||
fn capture_action<A: 'static>(
|
fn capture_action<A: Action>(
|
||||||
mut self,
|
mut self,
|
||||||
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
|
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity().key_listeners.push((
|
self.stateless_interactivity().action_listeners.push((
|
||||||
TypeId::of::<A>(),
|
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();
|
let action = action.downcast_ref().unwrap();
|
||||||
if phase == DispatchPhase::Capture {
|
if phase == DispatchPhase::Capture {
|
||||||
listener(view, action, cx)
|
listener(view, action, cx)
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a listener for the given action, fires during the bubble event phase
|
/// 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,
|
mut self,
|
||||||
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
|
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity().key_listeners.push((
|
self.stateless_interactivity().action_listeners.push((
|
||||||
TypeId::of::<A>(),
|
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();
|
let action = action.downcast_ref().unwrap();
|
||||||
if phase == DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble {
|
||||||
listener(view, action, cx)
|
listener(view, action, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
self
|
self
|
||||||
|
@ -225,14 +211,11 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity().key_listeners.push((
|
self.stateless_interactivity()
|
||||||
TypeId::of::<KeyDownEvent>(),
|
.key_down_listeners
|
||||||
Box::new(move |view, event, _, phase, cx| {
|
.push(Box::new(move |view, event, phase, cx| {
|
||||||
let event = event.downcast_ref().unwrap();
|
listener(view, event, phase, cx)
|
||||||
listener(view, event, phase, cx);
|
}));
|
||||||
None
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,14 +226,11 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity().key_listeners.push((
|
self.stateless_interactivity()
|
||||||
TypeId::of::<KeyUpEvent>(),
|
.key_up_listeners
|
||||||
Box::new(move |view, event, _, phase, cx| {
|
.push(Box::new(move |view, event, phase, cx| {
|
||||||
let event = event.downcast_ref().unwrap();
|
listener(view, event, phase, cx)
|
||||||
listener(view, event, phase, cx);
|
}));
|
||||||
None
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,43 +376,6 @@ pub trait ElementInteractivity<V: 'static>: 'static {
|
||||||
fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
|
fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
|
||||||
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
|
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
|
||||||
|
|
||||||
fn initialize<R>(
|
|
||||||
&mut self,
|
|
||||||
cx: &mut ViewContext<V>,
|
|
||||||
f: impl FnOnce(&mut ViewContext<V>) -> R,
|
|
||||||
) -> R {
|
|
||||||
if let Some(stateful) = self.as_stateful_mut() {
|
|
||||||
cx.with_element_id(stateful.id.clone(), |global_id, cx| {
|
|
||||||
// In addition to any key down/up listeners registered directly on the element,
|
|
||||||
// we also add a key listener to match actions from the keymap.
|
|
||||||
stateful.key_listeners.push((
|
|
||||||
TypeId::of::<KeyDownEvent>(),
|
|
||||||
Box::new(move |_, key_down, context, phase, cx| {
|
|
||||||
if phase == DispatchPhase::Bubble {
|
|
||||||
let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
|
|
||||||
if let KeyMatch::Some(action) =
|
|
||||||
cx.match_keystroke(&global_id, &key_down.keystroke, context)
|
|
||||||
{
|
|
||||||
return Some(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
|
|
||||||
cx.with_key_dispatch_context(stateful.dispatch_context.clone(), |cx| {
|
|
||||||
cx.with_key_listeners(mem::take(&mut stateful.key_listeners), f)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
let stateless = self.as_stateless_mut();
|
|
||||||
cx.with_key_dispatch_context(stateless.dispatch_context.clone(), |cx| {
|
|
||||||
cx.with_key_listeners(mem::take(&mut stateless.key_listeners), f)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn refine_style(
|
fn refine_style(
|
||||||
&self,
|
&self,
|
||||||
style: &mut Style,
|
style: &mut Style,
|
||||||
|
@ -487,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(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
|
@ -808,12 +771,14 @@ impl<V: 'static> ElementInteractivity<V> for StatefulInteractivity<V> {
|
||||||
type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
|
type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
|
||||||
|
|
||||||
pub struct StatelessInteractivity<V> {
|
pub struct StatelessInteractivity<V> {
|
||||||
pub dispatch_context: DispatchContext,
|
pub dispatch_context: KeyContext,
|
||||||
pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
|
pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
|
||||||
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
|
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
|
||||||
pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
|
pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
|
||||||
pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<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 hover_style: StyleRefinement,
|
||||||
pub group_hover_style: Option<GroupStyle>,
|
pub group_hover_style: Option<GroupStyle>,
|
||||||
drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
|
drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
|
||||||
|
@ -910,12 +875,14 @@ impl InteractiveElementState {
|
||||||
impl<V> Default for StatelessInteractivity<V> {
|
impl<V> Default for StatelessInteractivity<V> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
dispatch_context: DispatchContext::default(),
|
dispatch_context: KeyContext::default(),
|
||||||
mouse_down_listeners: SmallVec::new(),
|
mouse_down_listeners: SmallVec::new(),
|
||||||
mouse_up_listeners: SmallVec::new(),
|
mouse_up_listeners: SmallVec::new(),
|
||||||
mouse_move_listeners: SmallVec::new(),
|
mouse_move_listeners: SmallVec::new(),
|
||||||
scroll_wheel_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(),
|
hover_style: StyleRefinement::default(),
|
||||||
group_hover_style: None,
|
group_hover_style: None,
|
||||||
drag_over_styles: SmallVec::new(),
|
drag_over_styles: SmallVec::new(),
|
||||||
|
@ -1250,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(crate) type TooltipBuilder<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
|
||||||
|
|
||||||
pub type KeyListener<V> = Box<
|
pub(crate) type KeyDownListener<V> =
|
||||||
dyn Fn(
|
Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
|
||||||
&mut V,
|
|
||||||
&dyn Any,
|
pub(crate) type KeyUpListener<V> =
|
||||||
&[&DispatchContext],
|
Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
|
||||||
DispatchPhase,
|
|
||||||
&mut ViewContext<V>,
|
pub type ActionListener<V> =
|
||||||
) -> Option<Box<dyn Action>>
|
Box<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static>;
|
||||||
+ 'static,
|
|
||||||
>;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
@ -1282,9 +1247,10 @@ mod test {
|
||||||
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
|
||||||
div().id("testview").child(
|
div().id("testview").child(
|
||||||
div()
|
div()
|
||||||
|
.context("test")
|
||||||
|
.track_focus(&self.focus_handle)
|
||||||
.on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
|
.on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
|
||||||
.on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)
|
.on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true),
|
||||||
.track_focus(&self.focus_handle),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
456
crates/gpui2/src/key_dispatch.rs
Normal file
456
crates/gpui2/src/key_dispatch.rs
Normal file
|
@ -0,0 +1,456 @@
|
||||||
|
use crate::{
|
||||||
|
build_action_from_type, Action, Bounds, DispatchPhase, Element, FocusEvent, FocusHandle,
|
||||||
|
FocusId, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent, Pixels,
|
||||||
|
Style, StyleRefinement, ViewContext, WindowContext,
|
||||||
|
};
|
||||||
|
use collections::HashMap;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use refineable::Refineable;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use std::{
|
||||||
|
any::{Any, TypeId},
|
||||||
|
rc::Rc,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
|
pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
|
||||||
|
pub type FocusListener<V> =
|
||||||
|
Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||||
|
pub struct DispatchNodeId(usize);
|
||||||
|
|
||||||
|
pub(crate) struct DispatchTree {
|
||||||
|
node_stack: Vec<DispatchNodeId>,
|
||||||
|
context_stack: Vec<KeyContext>,
|
||||||
|
nodes: Vec<DispatchNode>,
|
||||||
|
focusable_node_ids: HashMap<FocusId, DispatchNodeId>,
|
||||||
|
keystroke_matchers: HashMap<SmallVec<[KeyContext; 4]>, KeystrokeMatcher>,
|
||||||
|
keymap: Arc<Mutex<Keymap>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct DispatchNode {
|
||||||
|
pub key_listeners: SmallVec<[KeyListener; 2]>,
|
||||||
|
pub action_listeners: SmallVec<[DispatchActionListener; 16]>,
|
||||||
|
pub context: KeyContext,
|
||||||
|
parent: Option<DispatchNodeId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct DispatchActionListener {
|
||||||
|
pub(crate) action_type: TypeId,
|
||||||
|
pub(crate) listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DispatchTree {
|
||||||
|
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
|
||||||
|
Self {
|
||||||
|
node_stack: Vec::new(),
|
||||||
|
context_stack: Vec::new(),
|
||||||
|
nodes: Vec::new(),
|
||||||
|
focusable_node_ids: HashMap::default(),
|
||||||
|
keystroke_matchers: HashMap::default(),
|
||||||
|
keymap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.node_stack.clear();
|
||||||
|
self.nodes.clear();
|
||||||
|
self.context_stack.clear();
|
||||||
|
self.focusable_node_ids.clear();
|
||||||
|
self.keystroke_matchers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_node(&mut self, context: KeyContext, old_dispatcher: &mut Self) {
|
||||||
|
let parent = self.node_stack.last().copied();
|
||||||
|
let node_id = DispatchNodeId(self.nodes.len());
|
||||||
|
self.nodes.push(DispatchNode {
|
||||||
|
parent,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
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
|
||||||
|
.remove_entry(self.context_stack.as_slice())
|
||||||
|
{
|
||||||
|
self.keystroke_matchers.insert(context_stack, matcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop_node(&mut self) {
|
||||||
|
let node_id = self.node_stack.pop().unwrap();
|
||||||
|
if !self.nodes[node_id.0].context.is_empty() {
|
||||||
|
self.context_stack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_key_event(&mut self, listener: KeyListener) {
|
||||||
|
self.active_node().key_listeners.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_action(
|
||||||
|
&mut self,
|
||||||
|
action_type: TypeId,
|
||||||
|
listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
|
||||||
|
) {
|
||||||
|
self.active_node()
|
||||||
|
.action_listeners
|
||||||
|
.push(DispatchActionListener {
|
||||||
|
action_type,
|
||||||
|
listener,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_focusable(&mut self, focus_id: FocusId) {
|
||||||
|
self.focusable_node_ids
|
||||||
|
.insert(focus_id, self.active_node_id());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool {
|
||||||
|
if parent == child {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(parent_node_id) = self.focusable_node_ids.get(&parent) {
|
||||||
|
let mut current_node_id = self.focusable_node_ids.get(&child).copied();
|
||||||
|
while let Some(node_id) = current_node_id {
|
||||||
|
if node_id == *parent_node_id {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
current_node_id = self.nodes[node_id.0].parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn available_actions(&self, target: FocusId) -> Vec<Box<dyn Action>> {
|
||||||
|
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 {
|
||||||
|
actions.extend(build_action_from_type(action_type).log_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispatch_key(
|
||||||
|
&mut self,
|
||||||
|
keystroke: &Keystroke,
|
||||||
|
context: &[KeyContext],
|
||||||
|
) -> Option<Box<dyn Action>> {
|
||||||
|
if !self.keystroke_matchers.contains_key(context) {
|
||||||
|
let keystroke_contexts = context.iter().cloned().collect();
|
||||||
|
self.keystroke_matchers.insert(
|
||||||
|
keystroke_contexts,
|
||||||
|
KeystrokeMatcher::new(self.keymap.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let keystroke_matcher = self.keystroke_matchers.get_mut(context).unwrap();
|
||||||
|
if let KeyMatch::Some(action) = keystroke_matcher.match_keystroke(keystroke, context) {
|
||||||
|
// Clear all pending keystrokes when an action has been found.
|
||||||
|
for keystroke_matcher in self.keystroke_matchers.values_mut() {
|
||||||
|
keystroke_matcher.clear_pending();
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(action)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispatch_path(&self, target: DispatchNodeId) -> SmallVec<[DispatchNodeId; 32]> {
|
||||||
|
let mut dispatch_path: SmallVec<[DispatchNodeId; 32]> = SmallVec::new();
|
||||||
|
let mut current_node_id = Some(target);
|
||||||
|
while let Some(node_id) = current_node_id {
|
||||||
|
dispatch_path.push(node_id);
|
||||||
|
current_node_id = self.nodes[node_id.0].parent;
|
||||||
|
}
|
||||||
|
dispatch_path.reverse(); // Reverse the path so it goes from the root to the focused node.
|
||||||
|
dispatch_path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode {
|
||||||
|
&self.nodes[node_id.0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active_node(&mut self) -> &mut DispatchNode {
|
||||||
|
let active_node_id = self.active_node_id();
|
||||||
|
&mut self.nodes[active_node_id.0]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focusable_node_id(&self, target: FocusId) -> Option<DispatchNodeId> {
|
||||||
|
self.focusable_node_ids.get(&target).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active_node_id(&self) -> DispatchNodeId {
|
||||||
|
*self.node_stack.last().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait KeyDispatch<V: 'static>: 'static {
|
||||||
|
fn as_focusable(&self) -> Option<&FocusableKeyDispatch<V>>;
|
||||||
|
fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch<V>>;
|
||||||
|
fn key_context(&self) -> &KeyContext;
|
||||||
|
fn key_context_mut(&mut self) -> &mut KeyContext;
|
||||||
|
|
||||||
|
fn initialize<R>(
|
||||||
|
&mut self,
|
||||||
|
focus_handle: Option<FocusHandle>,
|
||||||
|
cx: &mut ViewContext<V>,
|
||||||
|
f: impl FnOnce(Option<FocusHandle>, &mut ViewContext<V>) -> R,
|
||||||
|
) -> R {
|
||||||
|
let focus_handle = if let Some(focusable) = self.as_focusable_mut() {
|
||||||
|
let focus_handle = focusable
|
||||||
|
.focus_handle
|
||||||
|
.get_or_insert_with(|| focus_handle.unwrap_or_else(|| cx.focus_handle()))
|
||||||
|
.clone();
|
||||||
|
for listener in focusable.focus_listeners.drain(..) {
|
||||||
|
let focus_handle = focus_handle.clone();
|
||||||
|
cx.on_focus_changed(move |view, event, cx| {
|
||||||
|
listener(view, &focus_handle, event, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(focus_handle)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.with_key_dispatch(self.key_context().clone(), focus_handle, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refine_style(&self, style: &mut Style, cx: &WindowContext) {
|
||||||
|
if let Some(focusable) = self.as_focusable() {
|
||||||
|
let focus_handle = focusable
|
||||||
|
.focus_handle
|
||||||
|
.as_ref()
|
||||||
|
.expect("must call initialize before refine_style");
|
||||||
|
if focus_handle.contains_focused(cx) {
|
||||||
|
style.refine(&focusable.focus_in_style);
|
||||||
|
}
|
||||||
|
|
||||||
|
if focus_handle.within_focused(cx) {
|
||||||
|
style.refine(&focusable.in_focus_style);
|
||||||
|
}
|
||||||
|
|
||||||
|
if focus_handle.is_focused(cx) {
|
||||||
|
style.refine(&focusable.focus_style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
|
||||||
|
if let Some(focusable) = self.as_focusable() {
|
||||||
|
let focus_handle = focusable
|
||||||
|
.focus_handle
|
||||||
|
.clone()
|
||||||
|
.expect("must call initialize before paint");
|
||||||
|
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||||
|
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||||
|
if !cx.default_prevented() {
|
||||||
|
cx.focus(&focus_handle);
|
||||||
|
cx.prevent_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FocusableKeyDispatch<V> {
|
||||||
|
pub non_focusable: NonFocusableKeyDispatch,
|
||||||
|
pub focus_handle: Option<FocusHandle>,
|
||||||
|
pub focus_listeners: FocusListeners<V>,
|
||||||
|
pub focus_style: StyleRefinement,
|
||||||
|
pub focus_in_style: StyleRefinement,
|
||||||
|
pub in_focus_style: StyleRefinement,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> FocusableKeyDispatch<V> {
|
||||||
|
pub fn new(non_focusable: NonFocusableKeyDispatch) -> Self {
|
||||||
|
Self {
|
||||||
|
non_focusable,
|
||||||
|
focus_handle: None,
|
||||||
|
focus_listeners: FocusListeners::default(),
|
||||||
|
focus_style: StyleRefinement::default(),
|
||||||
|
focus_in_style: StyleRefinement::default(),
|
||||||
|
in_focus_style: StyleRefinement::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tracked(non_focusable: NonFocusableKeyDispatch, handle: &FocusHandle) -> Self {
|
||||||
|
Self {
|
||||||
|
non_focusable,
|
||||||
|
focus_handle: Some(handle.clone()),
|
||||||
|
focus_listeners: FocusListeners::default(),
|
||||||
|
focus_style: StyleRefinement::default(),
|
||||||
|
focus_in_style: StyleRefinement::default(),
|
||||||
|
in_focus_style: StyleRefinement::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> KeyDispatch<V> for FocusableKeyDispatch<V> {
|
||||||
|
fn as_focusable(&self) -> Option<&FocusableKeyDispatch<V>> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch<V>> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_context(&self) -> &KeyContext {
|
||||||
|
&self.non_focusable.key_context
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_context_mut(&mut self) -> &mut KeyContext {
|
||||||
|
&mut self.non_focusable.key_context
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NonFocusableKeyDispatch {
|
||||||
|
pub(crate) key_context: KeyContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> KeyDispatch<V> for NonFocusableKeyDispatch {
|
||||||
|
fn as_focusable(&self) -> Option<&FocusableKeyDispatch<V>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch<V>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_context(&self) -> &KeyContext {
|
||||||
|
&self.key_context
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_context_mut(&mut self) -> &mut KeyContext {
|
||||||
|
&mut self.key_context
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Focusable<V: 'static>: Element<V> {
|
||||||
|
fn focus_listeners(&mut self) -> &mut FocusListeners<V>;
|
||||||
|
fn set_focus_style(&mut self, style: StyleRefinement);
|
||||||
|
fn set_focus_in_style(&mut self, style: StyleRefinement);
|
||||||
|
fn set_in_focus_style(&mut self, style: StyleRefinement);
|
||||||
|
|
||||||
|
fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.set_focus_style(f(StyleRefinement::default()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn focus_in(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.set_focus_in_style(f(StyleRefinement::default()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.set_in_focus_style(f(StyleRefinement::default()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_focus(
|
||||||
|
mut self,
|
||||||
|
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.focus_listeners()
|
||||||
|
.push(Box::new(move |view, focus_handle, event, cx| {
|
||||||
|
if event.focused.as_ref() == Some(focus_handle) {
|
||||||
|
listener(view, event, cx)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_blur(
|
||||||
|
mut self,
|
||||||
|
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.focus_listeners()
|
||||||
|
.push(Box::new(move |view, focus_handle, event, cx| {
|
||||||
|
if event.blurred.as_ref() == Some(focus_handle) {
|
||||||
|
listener(view, event, cx)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_focus_in(
|
||||||
|
mut self,
|
||||||
|
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.focus_listeners()
|
||||||
|
.push(Box::new(move |view, focus_handle, event, cx| {
|
||||||
|
let descendant_blurred = event
|
||||||
|
.blurred
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |blurred| focus_handle.contains(blurred, cx));
|
||||||
|
let descendant_focused = event
|
||||||
|
.focused
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |focused| focus_handle.contains(focused, cx));
|
||||||
|
|
||||||
|
if !descendant_blurred && descendant_focused {
|
||||||
|
listener(view, event, cx)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_focus_out(
|
||||||
|
mut self,
|
||||||
|
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.focus_listeners()
|
||||||
|
.push(Box::new(move |view, focus_handle, event, cx| {
|
||||||
|
let descendant_blurred = event
|
||||||
|
.blurred
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |blurred| focus_handle.contains(blurred, cx));
|
||||||
|
let descendant_focused = event
|
||||||
|
.focused
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |focused| focus_handle.contains(focused, cx));
|
||||||
|
if descendant_blurred && !descendant_focused {
|
||||||
|
listener(view, event, cx)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::{Action, DispatchContext, DispatchContextPredicate, KeyMatch, Keystroke};
|
use crate::{Action, KeyBindingContextPredicate, KeyContext, KeyMatch, Keystroke};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
pub struct KeyBinding {
|
pub struct KeyBinding {
|
||||||
action: Box<dyn Action>,
|
action: Box<dyn Action>,
|
||||||
pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
|
pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
|
||||||
pub(super) context_predicate: Option<DispatchContextPredicate>,
|
pub(super) context_predicate: Option<KeyBindingContextPredicate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyBinding {
|
impl KeyBinding {
|
||||||
|
@ -15,7 +15,7 @@ impl KeyBinding {
|
||||||
|
|
||||||
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
|
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
|
||||||
let context = if let Some(context) = context {
|
let context = if let Some(context) = context {
|
||||||
Some(DispatchContextPredicate::parse(context)?)
|
Some(KeyBindingContextPredicate::parse(context)?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -32,7 +32,7 @@ impl KeyBinding {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn matches_context(&self, contexts: &[&DispatchContext]) -> bool {
|
pub fn matches_context(&self, contexts: &[KeyContext]) -> bool {
|
||||||
self.context_predicate
|
self.context_predicate
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|predicate| predicate.eval(contexts))
|
.map(|predicate| predicate.eval(contexts))
|
||||||
|
@ -42,7 +42,7 @@ impl KeyBinding {
|
||||||
pub fn match_keystrokes(
|
pub fn match_keystrokes(
|
||||||
&self,
|
&self,
|
||||||
pending_keystrokes: &[Keystroke],
|
pending_keystrokes: &[Keystroke],
|
||||||
contexts: &[&DispatchContext],
|
contexts: &[KeyContext],
|
||||||
) -> KeyMatch {
|
) -> KeyMatch {
|
||||||
if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
|
if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
|
||||||
&& self.matches_context(contexts)
|
&& self.matches_context(contexts)
|
||||||
|
@ -61,7 +61,7 @@ impl KeyBinding {
|
||||||
pub fn keystrokes_for_action(
|
pub fn keystrokes_for_action(
|
||||||
&self,
|
&self,
|
||||||
action: &dyn Action,
|
action: &dyn Action,
|
||||||
contexts: &[&DispatchContext],
|
contexts: &[KeyContext],
|
||||||
) -> Option<SmallVec<[Keystroke; 2]>> {
|
) -> Option<SmallVec<[Keystroke; 2]>> {
|
||||||
if self.action.partial_eq(action) && self.matches_context(contexts) {
|
if self.action.partial_eq(action) && self.matches_context(contexts) {
|
||||||
Some(self.keystrokes.clone())
|
Some(self.keystrokes.clone())
|
||||||
|
|
449
crates/gpui2/src/keymap/context.rs
Normal file
449
crates/gpui2/src/keymap/context.rs
Normal file
|
@ -0,0 +1,449 @@
|
||||||
|
use crate::SharedString;
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Eq, PartialEq, Hash)]
|
||||||
|
pub struct KeyContext(SmallVec<[ContextEntry; 8]>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
struct ContextEntry {
|
||||||
|
key: SharedString,
|
||||||
|
value: Option<SharedString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for KeyContext {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &'a str) -> Result<Self> {
|
||||||
|
Self::parse(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyContext {
|
||||||
|
pub fn parse(source: &str) -> Result<Self> {
|
||||||
|
let mut context = Self::default();
|
||||||
|
let source = skip_whitespace(source);
|
||||||
|
Self::parse_expr(&source, &mut context)?;
|
||||||
|
Ok(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
|
||||||
|
if source.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = source
|
||||||
|
.chars()
|
||||||
|
.take_while(|c| is_identifier_char(*c))
|
||||||
|
.collect::<String>();
|
||||||
|
source = skip_whitespace(&source[key.len()..]);
|
||||||
|
if let Some(suffix) = source.strip_prefix('=') {
|
||||||
|
source = skip_whitespace(suffix);
|
||||||
|
let value = source
|
||||||
|
.chars()
|
||||||
|
.take_while(|c| is_identifier_char(*c))
|
||||||
|
.collect::<String>();
|
||||||
|
source = skip_whitespace(&source[value.len()..]);
|
||||||
|
context.set(key, value);
|
||||||
|
} else {
|
||||||
|
context.add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::parse_expr(source, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.0.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extend(&mut self, other: &Self) {
|
||||||
|
for entry in &other.0 {
|
||||||
|
if !self.contains(&entry.key) {
|
||||||
|
self.0.push(entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add<I: Into<SharedString>>(&mut self, identifier: I) {
|
||||||
|
let key = identifier.into();
|
||||||
|
|
||||||
|
if !self.contains(&key) {
|
||||||
|
self.0.push(ContextEntry { key, value: None })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
|
||||||
|
let key = key.into();
|
||||||
|
if !self.contains(&key) {
|
||||||
|
self.0.push(ContextEntry {
|
||||||
|
key,
|
||||||
|
value: Some(value.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, key: &str) -> bool {
|
||||||
|
self.0.iter().any(|entry| entry.key.as_ref() == key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, key: &str) -> Option<&SharedString> {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.find(|entry| entry.key.as_ref() == key)?
|
||||||
|
.value
|
||||||
|
.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for KeyContext {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut entries = self.0.iter().peekable();
|
||||||
|
while let Some(entry) = entries.next() {
|
||||||
|
if let Some(ref value) = entry.value {
|
||||||
|
write!(f, "{}={}", entry.key, value)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", entry.key)?;
|
||||||
|
}
|
||||||
|
if entries.peek().is_some() {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
pub enum KeyBindingContextPredicate {
|
||||||
|
Identifier(SharedString),
|
||||||
|
Equal(SharedString, SharedString),
|
||||||
|
NotEqual(SharedString, SharedString),
|
||||||
|
Child(
|
||||||
|
Box<KeyBindingContextPredicate>,
|
||||||
|
Box<KeyBindingContextPredicate>,
|
||||||
|
),
|
||||||
|
Not(Box<KeyBindingContextPredicate>),
|
||||||
|
And(
|
||||||
|
Box<KeyBindingContextPredicate>,
|
||||||
|
Box<KeyBindingContextPredicate>,
|
||||||
|
),
|
||||||
|
Or(
|
||||||
|
Box<KeyBindingContextPredicate>,
|
||||||
|
Box<KeyBindingContextPredicate>,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyBindingContextPredicate {
|
||||||
|
pub fn parse(source: &str) -> Result<Self> {
|
||||||
|
let source = skip_whitespace(source);
|
||||||
|
let (predicate, rest) = Self::parse_expr(source, 0)?;
|
||||||
|
if let Some(next) = rest.chars().next() {
|
||||||
|
Err(anyhow!("unexpected character {next:?}"))
|
||||||
|
} else {
|
||||||
|
Ok(predicate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval(&self, contexts: &[KeyContext]) -> bool {
|
||||||
|
let Some(context) = contexts.last() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
match self {
|
||||||
|
Self::Identifier(name) => context.contains(name),
|
||||||
|
Self::Equal(left, right) => context
|
||||||
|
.get(left)
|
||||||
|
.map(|value| value == right)
|
||||||
|
.unwrap_or(false),
|
||||||
|
Self::NotEqual(left, right) => context
|
||||||
|
.get(left)
|
||||||
|
.map(|value| value != right)
|
||||||
|
.unwrap_or(true),
|
||||||
|
Self::Not(pred) => !pred.eval(contexts),
|
||||||
|
Self::Child(parent, child) => {
|
||||||
|
parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
|
||||||
|
}
|
||||||
|
Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
|
||||||
|
Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
|
||||||
|
type Op = fn(
|
||||||
|
KeyBindingContextPredicate,
|
||||||
|
KeyBindingContextPredicate,
|
||||||
|
) -> Result<KeyBindingContextPredicate>;
|
||||||
|
|
||||||
|
let (mut predicate, rest) = Self::parse_primary(source)?;
|
||||||
|
source = rest;
|
||||||
|
|
||||||
|
'parse: loop {
|
||||||
|
for (operator, precedence, constructor) in [
|
||||||
|
(">", PRECEDENCE_CHILD, Self::new_child as Op),
|
||||||
|
("&&", PRECEDENCE_AND, Self::new_and as Op),
|
||||||
|
("||", PRECEDENCE_OR, Self::new_or as Op),
|
||||||
|
("==", PRECEDENCE_EQ, Self::new_eq as Op),
|
||||||
|
("!=", PRECEDENCE_EQ, Self::new_neq as Op),
|
||||||
|
] {
|
||||||
|
if source.starts_with(operator) && precedence >= min_precedence {
|
||||||
|
source = skip_whitespace(&source[operator.len()..]);
|
||||||
|
let (right, rest) = Self::parse_expr(source, precedence + 1)?;
|
||||||
|
predicate = constructor(predicate, right)?;
|
||||||
|
source = rest;
|
||||||
|
continue 'parse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((predicate, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
|
||||||
|
let next = source
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow!("unexpected eof"))?;
|
||||||
|
match next {
|
||||||
|
'(' => {
|
||||||
|
source = skip_whitespace(&source[1..]);
|
||||||
|
let (predicate, rest) = Self::parse_expr(source, 0)?;
|
||||||
|
if rest.starts_with(')') {
|
||||||
|
source = skip_whitespace(&rest[1..]);
|
||||||
|
Ok((predicate, source))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("expected a ')'"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'!' => {
|
||||||
|
let source = skip_whitespace(&source[1..]);
|
||||||
|
let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
|
||||||
|
Ok((KeyBindingContextPredicate::Not(Box::new(predicate)), source))
|
||||||
|
}
|
||||||
|
_ if is_identifier_char(next) => {
|
||||||
|
let len = source
|
||||||
|
.find(|c: char| !is_identifier_char(c))
|
||||||
|
.unwrap_or(source.len());
|
||||||
|
let (identifier, rest) = source.split_at(len);
|
||||||
|
source = skip_whitespace(rest);
|
||||||
|
Ok((
|
||||||
|
KeyBindingContextPredicate::Identifier(identifier.to_string().into()),
|
||||||
|
source,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => Err(anyhow!("unexpected character {next:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_or(self, other: Self) -> Result<Self> {
|
||||||
|
Ok(Self::Or(Box::new(self), Box::new(other)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_and(self, other: Self) -> Result<Self> {
|
||||||
|
Ok(Self::And(Box::new(self), Box::new(other)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_child(self, other: Self) -> Result<Self> {
|
||||||
|
Ok(Self::Child(Box::new(self), Box::new(other)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_eq(self, other: Self) -> Result<Self> {
|
||||||
|
if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
|
||||||
|
Ok(Self::Equal(left, right))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("operands must be identifiers"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_neq(self, other: Self) -> Result<Self> {
|
||||||
|
if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
|
||||||
|
Ok(Self::NotEqual(left, right))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("operands must be identifiers"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRECEDENCE_CHILD: u32 = 1;
|
||||||
|
const PRECEDENCE_OR: u32 = 2;
|
||||||
|
const PRECEDENCE_AND: u32 = 3;
|
||||||
|
const PRECEDENCE_EQ: u32 = 4;
|
||||||
|
const PRECEDENCE_NOT: u32 = 5;
|
||||||
|
|
||||||
|
fn is_identifier_char(c: char) -> bool {
|
||||||
|
c.is_alphanumeric() || c == '_' || c == '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_whitespace(source: &str) -> &str {
|
||||||
|
let len = source
|
||||||
|
.find(|c: char| !c.is_whitespace())
|
||||||
|
.unwrap_or(source.len());
|
||||||
|
&source[len..]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate as gpui;
|
||||||
|
use KeyBindingContextPredicate::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_actions_definition() {
|
||||||
|
{
|
||||||
|
actions!(A, B, C, D, E, F, G);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
actions!(
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
D,
|
||||||
|
E,
|
||||||
|
F,
|
||||||
|
G, // Don't wrap, test the trailing comma
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_context() {
|
||||||
|
let mut expected = KeyContext::default();
|
||||||
|
expected.add("baz");
|
||||||
|
expected.set("foo", "bar");
|
||||||
|
assert_eq!(KeyContext::parse("baz foo=bar").unwrap(), expected);
|
||||||
|
assert_eq!(KeyContext::parse("baz foo = bar").unwrap(), expected);
|
||||||
|
assert_eq!(
|
||||||
|
KeyContext::parse(" baz foo = bar baz").unwrap(),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
assert_eq!(KeyContext::parse(" baz foo = bar").unwrap(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_identifiers() {
|
||||||
|
// Identifiers
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse("abc12").unwrap(),
|
||||||
|
Identifier("abc12".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse("_1a").unwrap(),
|
||||||
|
Identifier("_1a".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_negations() {
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse("!abc").unwrap(),
|
||||||
|
Not(Box::new(Identifier("abc".into())))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse(" ! ! abc").unwrap(),
|
||||||
|
Not(Box::new(Not(Box::new(Identifier("abc".into())))))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_equality_operators() {
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse("a == b").unwrap(),
|
||||||
|
Equal("a".into(), "b".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse("c!=d").unwrap(),
|
||||||
|
NotEqual("c".into(), "d".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse("c == !d")
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string(),
|
||||||
|
"operands must be identifiers"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_boolean_operators() {
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse("a || b").unwrap(),
|
||||||
|
Or(
|
||||||
|
Box::new(Identifier("a".into())),
|
||||||
|
Box::new(Identifier("b".into()))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse("a || !b && c").unwrap(),
|
||||||
|
Or(
|
||||||
|
Box::new(Identifier("a".into())),
|
||||||
|
Box::new(And(
|
||||||
|
Box::new(Not(Box::new(Identifier("b".into())))),
|
||||||
|
Box::new(Identifier("c".into()))
|
||||||
|
))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse("a && b || c&&d").unwrap(),
|
||||||
|
Or(
|
||||||
|
Box::new(And(
|
||||||
|
Box::new(Identifier("a".into())),
|
||||||
|
Box::new(Identifier("b".into()))
|
||||||
|
)),
|
||||||
|
Box::new(And(
|
||||||
|
Box::new(Identifier("c".into())),
|
||||||
|
Box::new(Identifier("d".into()))
|
||||||
|
))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse("a == b && c || d == e && f").unwrap(),
|
||||||
|
Or(
|
||||||
|
Box::new(And(
|
||||||
|
Box::new(Equal("a".into(), "b".into())),
|
||||||
|
Box::new(Identifier("c".into()))
|
||||||
|
)),
|
||||||
|
Box::new(And(
|
||||||
|
Box::new(Equal("d".into(), "e".into())),
|
||||||
|
Box::new(Identifier("f".into()))
|
||||||
|
))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse("a && b && c && d").unwrap(),
|
||||||
|
And(
|
||||||
|
Box::new(And(
|
||||||
|
Box::new(And(
|
||||||
|
Box::new(Identifier("a".into())),
|
||||||
|
Box::new(Identifier("b".into()))
|
||||||
|
)),
|
||||||
|
Box::new(Identifier("c".into())),
|
||||||
|
)),
|
||||||
|
Box::new(Identifier("d".into()))
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_parenthesized_expressions() {
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
|
||||||
|
And(
|
||||||
|
Box::new(Identifier("a".into())),
|
||||||
|
Box::new(Or(
|
||||||
|
Box::new(Equal("b".into(), "c".into())),
|
||||||
|
Box::new(NotEqual("d".into(), "e".into())),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
KeyBindingContextPredicate::parse(" ( a || b ) ").unwrap(),
|
||||||
|
Or(
|
||||||
|
Box::new(Identifier("a".into())),
|
||||||
|
Box::new(Identifier("b".into())),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{DispatchContextPredicate, KeyBinding, Keystroke};
|
use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke};
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{any::TypeId, collections::HashMap};
|
use std::{any::TypeId, collections::HashMap};
|
||||||
|
@ -11,7 +11,7 @@ pub struct Keymap {
|
||||||
bindings: Vec<KeyBinding>,
|
bindings: Vec<KeyBinding>,
|
||||||
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
|
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
|
||||||
disabled_keystrokes:
|
disabled_keystrokes:
|
||||||
HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<DispatchContextPredicate>>>,
|
HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeyBindingContextPredicate>>>,
|
||||||
version: KeymapVersion,
|
version: KeymapVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use crate::{Action, DispatchContext, Keymap, KeymapVersion, Keystroke};
|
use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct KeyMatcher {
|
pub struct KeystrokeMatcher {
|
||||||
pending_keystrokes: Vec<Keystroke>,
|
pending_keystrokes: Vec<Keystroke>,
|
||||||
keymap: Arc<Mutex<Keymap>>,
|
keymap: Arc<Mutex<Keymap>>,
|
||||||
keymap_version: KeymapVersion,
|
keymap_version: KeymapVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyMatcher {
|
impl KeystrokeMatcher {
|
||||||
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
|
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
|
||||||
let keymap_version = keymap.lock().version();
|
let keymap_version = keymap.lock().version();
|
||||||
Self {
|
Self {
|
||||||
|
@ -44,7 +44,7 @@ impl KeyMatcher {
|
||||||
pub fn match_keystroke(
|
pub fn match_keystroke(
|
||||||
&mut self,
|
&mut self,
|
||||||
keystroke: &Keystroke,
|
keystroke: &Keystroke,
|
||||||
context_stack: &[&DispatchContext],
|
context_stack: &[KeyContext],
|
||||||
) -> KeyMatch {
|
) -> KeyMatch {
|
||||||
let keymap = self.keymap.lock();
|
let keymap = self.keymap.lock();
|
||||||
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
|
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
|
||||||
|
@ -86,7 +86,7 @@ impl KeyMatcher {
|
||||||
pub fn keystrokes_for_action(
|
pub fn keystrokes_for_action(
|
||||||
&self,
|
&self,
|
||||||
action: &dyn Action,
|
action: &dyn Action,
|
||||||
contexts: &[&DispatchContext],
|
contexts: &[KeyContext],
|
||||||
) -> Option<SmallVec<[Keystroke; 2]>> {
|
) -> Option<SmallVec<[Keystroke; 2]>> {
|
||||||
self.keymap
|
self.keymap
|
||||||
.lock()
|
.lock()
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
mod binding;
|
mod binding;
|
||||||
|
mod context;
|
||||||
mod keymap;
|
mod keymap;
|
||||||
mod matcher;
|
mod matcher;
|
||||||
|
|
||||||
pub use binding::*;
|
pub use binding::*;
|
||||||
|
pub use context::*;
|
||||||
pub use keymap::*;
|
pub use keymap::*;
|
||||||
pub use matcher::*;
|
pub use matcher::*;
|
||||||
|
|
1
crates/gpui2/src/prelude.rs
Normal file
1
crates/gpui2/src/prelude.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub use crate::{Context, ParentElement, Refineable};
|
|
@ -184,10 +184,6 @@ impl AnyView {
|
||||||
.compute_layout(layout_id, available_space);
|
.compute_layout(layout_id, available_space);
|
||||||
(self.paint)(self, &mut rendered_element, cx);
|
(self.paint)(self, &mut rendered_element, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn draw_dispatch_stack(&self, cx: &mut WindowContext) {
|
|
||||||
(self.initialize)(self, cx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> Component<V> for AnyView {
|
impl<V: 'static> Component<V> for AnyView {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
build_action_from_type, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,
|
key_dispatch::DispatchActionListener, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,
|
||||||
AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
|
AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
|
||||||
DevicePixels, DispatchContext, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
|
DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
|
||||||
FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent,
|
EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
|
||||||
IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers,
|
InputEvent, IsZero, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext, Modifiers,
|
||||||
MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
|
MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
|
||||||
PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
|
PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
|
||||||
PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
|
PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
|
||||||
|
@ -60,16 +60,7 @@ pub enum DispatchPhase {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnyObserver = Box<dyn FnMut(&mut WindowContext) -> bool + 'static>;
|
type AnyObserver = Box<dyn FnMut(&mut WindowContext) -> bool + 'static>;
|
||||||
type AnyListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
|
type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
|
||||||
type AnyKeyListener = Box<
|
|
||||||
dyn Fn(
|
|
||||||
&dyn Any,
|
|
||||||
&[&DispatchContext],
|
|
||||||
DispatchPhase,
|
|
||||||
&mut WindowContext,
|
|
||||||
) -> Option<Box<dyn Action>>
|
|
||||||
+ 'static,
|
|
||||||
>;
|
|
||||||
type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + 'static>;
|
type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + 'static>;
|
||||||
type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> bool + 'static>;
|
type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> bool + 'static>;
|
||||||
|
|
||||||
|
@ -97,20 +88,10 @@ impl FocusId {
|
||||||
|
|
||||||
/// Obtains whether this handle contains the given handle in the most recently rendered frame.
|
/// Obtains whether this handle contains the given handle in the most recently rendered frame.
|
||||||
pub(crate) fn contains(&self, other: Self, cx: &WindowContext) -> bool {
|
pub(crate) fn contains(&self, other: Self, cx: &WindowContext) -> bool {
|
||||||
let mut ancestor = Some(other);
|
cx.window
|
||||||
while let Some(ancestor_id) = ancestor {
|
.current_frame
|
||||||
if *self == ancestor_id {
|
.dispatch_tree
|
||||||
return true;
|
.focus_contains(*self, other)
|
||||||
} else {
|
|
||||||
ancestor = cx
|
|
||||||
.window
|
|
||||||
.current_frame
|
|
||||||
.focus_parents_by_child
|
|
||||||
.get(&ancestor_id)
|
|
||||||
.copied();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,20 +208,31 @@ pub struct Window {
|
||||||
pub(crate) focus: Option<FocusId>,
|
pub(crate) focus: Option<FocusId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
// #[derive(Default)]
|
||||||
pub(crate) struct Frame {
|
pub(crate) struct Frame {
|
||||||
element_states: HashMap<GlobalElementId, AnyBox>,
|
element_states: HashMap<GlobalElementId, AnyBox>,
|
||||||
key_matchers: HashMap<GlobalElementId, KeyMatcher>,
|
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
|
||||||
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
|
pub(crate) dispatch_tree: DispatchTree,
|
||||||
pub(crate) focus_listeners: Vec<AnyFocusListener>,
|
pub(crate) focus_listeners: Vec<AnyFocusListener>,
|
||||||
pub(crate) key_dispatch_stack: Vec<KeyDispatchStackFrame>,
|
|
||||||
freeze_key_dispatch_stack: bool,
|
|
||||||
focus_parents_by_child: HashMap<FocusId, FocusId>,
|
|
||||||
pub(crate) scene_builder: SceneBuilder,
|
pub(crate) scene_builder: SceneBuilder,
|
||||||
z_index_stack: StackingOrder,
|
z_index_stack: StackingOrder,
|
||||||
content_mask_stack: Vec<ContentMask<Pixels>>,
|
content_mask_stack: Vec<ContentMask<Pixels>>,
|
||||||
element_offset_stack: Vec<Point<Pixels>>,
|
element_offset_stack: Vec<Point<Pixels>>,
|
||||||
focus_stack: Vec<FocusId>,
|
}
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
pub fn new(dispatch_tree: DispatchTree) -> Self {
|
||||||
|
Frame {
|
||||||
|
element_states: HashMap::default(),
|
||||||
|
mouse_listeners: HashMap::default(),
|
||||||
|
dispatch_tree,
|
||||||
|
focus_listeners: Vec::new(),
|
||||||
|
scene_builder: SceneBuilder::default(),
|
||||||
|
z_index_stack: StackingOrder::default(),
|
||||||
|
content_mask_stack: Vec::new(),
|
||||||
|
element_offset_stack: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
@ -309,8 +301,8 @@ impl Window {
|
||||||
layout_engine: TaffyLayoutEngine::new(),
|
layout_engine: TaffyLayoutEngine::new(),
|
||||||
root_view: None,
|
root_view: None,
|
||||||
element_id_stack: GlobalElementId::default(),
|
element_id_stack: GlobalElementId::default(),
|
||||||
previous_frame: Frame::default(),
|
previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone())),
|
||||||
current_frame: Frame::default(),
|
current_frame: Frame::new(DispatchTree::new(cx.keymap.clone())),
|
||||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||||
focus_listeners: SubscriberSet::new(),
|
focus_listeners: SubscriberSet::new(),
|
||||||
default_prevented: true,
|
default_prevented: true,
|
||||||
|
@ -328,18 +320,6 @@ impl Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When constructing the element tree, we maintain a stack of key dispatch frames until we
|
|
||||||
/// find the focused element. We interleave key listeners with dispatch contexts so we can use the
|
|
||||||
/// contexts when matching key events against the keymap. A key listener can be either an action
|
|
||||||
/// handler or a [KeyDown] / [KeyUp] event listener.
|
|
||||||
pub(crate) enum KeyDispatchStackFrame {
|
|
||||||
Listener {
|
|
||||||
event_type: TypeId,
|
|
||||||
listener: AnyKeyListener,
|
|
||||||
},
|
|
||||||
Context(DispatchContext),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Indicates which region of the window is visible. Content falling outside of this mask will not be
|
/// Indicates which region of the window is visible. Content falling outside of this mask will not be
|
||||||
/// rendered. Currently, only rectangular content masks are supported, but we give the mask its own type
|
/// rendered. Currently, only rectangular content masks are supported, but we give the mask its own type
|
||||||
/// to leave room to support more complex shapes in the future.
|
/// to leave room to support more complex shapes in the future.
|
||||||
|
@ -407,21 +387,16 @@ impl<'a> WindowContext<'a> {
|
||||||
|
|
||||||
/// Move focus to the element associated with the given `FocusHandle`.
|
/// Move focus to the element associated with the given `FocusHandle`.
|
||||||
pub fn focus(&mut self, handle: &FocusHandle) {
|
pub fn focus(&mut self, handle: &FocusHandle) {
|
||||||
if self.window.focus == Some(handle.id) {
|
let focus_id = handle.id;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.window.last_blur.is_none() {
|
if self.window.last_blur.is_none() {
|
||||||
self.window.last_blur = Some(self.window.focus);
|
self.window.last_blur = Some(self.window.focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.window.focus = Some(handle.id);
|
self.window.focus = Some(focus_id);
|
||||||
|
|
||||||
// self.window.current_frame.key_dispatch_stack.clear()
|
|
||||||
// self.window.root_view.initialize()
|
|
||||||
self.app.push_effect(Effect::FocusChanged {
|
self.app.push_effect(Effect::FocusChanged {
|
||||||
window_handle: self.window.handle,
|
window_handle: self.window.handle,
|
||||||
focused: Some(handle.id),
|
focused: Some(focus_id),
|
||||||
});
|
});
|
||||||
self.notify();
|
self.notify();
|
||||||
}
|
}
|
||||||
|
@ -441,11 +416,18 @@ impl<'a> WindowContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
|
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
|
||||||
self.defer(|cx| {
|
if let Some(focus_handle) = self.focused() {
|
||||||
cx.app.propagate_event = true;
|
self.defer(move |cx| {
|
||||||
let stack = cx.dispatch_stack();
|
if let Some(node_id) = cx
|
||||||
cx.dispatch_action_internal(action, &stack[..])
|
.window
|
||||||
})
|
.current_frame
|
||||||
|
.dispatch_tree
|
||||||
|
.focusable_node_id(focus_handle.id)
|
||||||
|
{
|
||||||
|
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
|
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
|
||||||
|
@ -731,6 +713,43 @@ 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,
|
||||||
|
) {
|
||||||
|
self.window
|
||||||
|
.current_frame
|
||||||
|
.dispatch_tree
|
||||||
|
.on_key_event(Rc::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,
|
||||||
|
) {
|
||||||
|
self.window.current_frame.dispatch_tree.on_action(
|
||||||
|
action_type,
|
||||||
|
Rc::new(move |action, phase, cx| handler(action, phase, cx)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// The position of the mouse relative to the window.
|
/// The position of the mouse relative to the window.
|
||||||
pub fn mouse_position(&self) -> Point<Pixels> {
|
pub fn mouse_position(&self) -> Point<Pixels> {
|
||||||
self.window.mouse_position
|
self.window.mouse_position
|
||||||
|
@ -1079,26 +1098,6 @@ impl<'a> WindowContext<'a> {
|
||||||
self.window.dirty = false;
|
self.window.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn dispatch_stack(&mut self) -> Vec<KeyDispatchStackFrame> {
|
|
||||||
let root_view = self.window.root_view.take().unwrap();
|
|
||||||
let window = &mut *self.window;
|
|
||||||
let mut spare_frame = Frame::default();
|
|
||||||
mem::swap(&mut spare_frame, &mut window.previous_frame);
|
|
||||||
|
|
||||||
self.start_frame();
|
|
||||||
|
|
||||||
root_view.draw_dispatch_stack(self);
|
|
||||||
|
|
||||||
let window = &mut *self.window;
|
|
||||||
// restore the old values of current and previous frame,
|
|
||||||
// putting the new frame into spare_frame.
|
|
||||||
mem::swap(&mut window.current_frame, &mut window.previous_frame);
|
|
||||||
mem::swap(&mut spare_frame, &mut window.previous_frame);
|
|
||||||
self.window.root_view = Some(root_view);
|
|
||||||
|
|
||||||
spare_frame.key_dispatch_stack
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rotate the current frame and the previous frame, then clear the current frame.
|
/// Rotate the current frame and the previous frame, then clear the current frame.
|
||||||
/// We repopulate all state in the current frame during each paint.
|
/// We repopulate all state in the current frame during each paint.
|
||||||
fn start_frame(&mut self) {
|
fn start_frame(&mut self) {
|
||||||
|
@ -1110,12 +1109,9 @@ impl<'a> WindowContext<'a> {
|
||||||
mem::swap(&mut window.previous_frame, &mut window.current_frame);
|
mem::swap(&mut window.previous_frame, &mut window.current_frame);
|
||||||
let frame = &mut window.current_frame;
|
let frame = &mut window.current_frame;
|
||||||
frame.element_states.clear();
|
frame.element_states.clear();
|
||||||
frame.key_matchers.clear();
|
|
||||||
frame.mouse_listeners.values_mut().for_each(Vec::clear);
|
frame.mouse_listeners.values_mut().for_each(Vec::clear);
|
||||||
frame.focus_listeners.clear();
|
frame.focus_listeners.clear();
|
||||||
frame.key_dispatch_stack.clear();
|
frame.dispatch_tree.clear();
|
||||||
frame.focus_parents_by_child.clear();
|
|
||||||
frame.freeze_key_dispatch_stack = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dispatch a mouse or keyboard event on the window.
|
/// Dispatch a mouse or keyboard event on the window.
|
||||||
|
@ -1177,146 +1173,172 @@ impl<'a> WindowContext<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(any_mouse_event) = event.mouse_event() {
|
if let Some(any_mouse_event) = event.mouse_event() {
|
||||||
if let Some(mut handlers) = self
|
self.dispatch_mouse_event(any_mouse_event);
|
||||||
.window
|
|
||||||
.current_frame
|
|
||||||
.mouse_listeners
|
|
||||||
.remove(&any_mouse_event.type_id())
|
|
||||||
{
|
|
||||||
// Because handlers may add other handlers, we sort every time.
|
|
||||||
handlers.sort_by(|(a, _), (b, _)| a.cmp(b));
|
|
||||||
|
|
||||||
// Capture phase, events bubble from back to front. Handlers for this phase are used for
|
|
||||||
// special purposes, such as detecting events outside of a given Bounds.
|
|
||||||
for (_, handler) in &mut handlers {
|
|
||||||
handler(any_mouse_event, DispatchPhase::Capture, self);
|
|
||||||
if !self.app.propagate_event {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bubble phase, where most normal handlers do their work.
|
|
||||||
if self.app.propagate_event {
|
|
||||||
for (_, handler) in handlers.iter_mut().rev() {
|
|
||||||
handler(any_mouse_event, DispatchPhase::Bubble, self);
|
|
||||||
if !self.app.propagate_event {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.app.propagate_event
|
|
||||||
&& any_mouse_event.downcast_ref::<MouseUpEvent>().is_some()
|
|
||||||
{
|
|
||||||
self.active_drag = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just in case any handlers added new handlers, which is weird, but possible.
|
|
||||||
handlers.extend(
|
|
||||||
self.window
|
|
||||||
.current_frame
|
|
||||||
.mouse_listeners
|
|
||||||
.get_mut(&any_mouse_event.type_id())
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|handlers| handlers.drain(..)),
|
|
||||||
);
|
|
||||||
self.window
|
|
||||||
.current_frame
|
|
||||||
.mouse_listeners
|
|
||||||
.insert(any_mouse_event.type_id(), handlers);
|
|
||||||
}
|
|
||||||
} else if let Some(any_key_event) = event.keyboard_event() {
|
} else if let Some(any_key_event) = event.keyboard_event() {
|
||||||
let key_dispatch_stack = mem::take(&mut self.window.current_frame.key_dispatch_stack);
|
self.dispatch_key_event(any_key_event);
|
||||||
let key_event_type = any_key_event.type_id();
|
|
||||||
let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new();
|
|
||||||
|
|
||||||
for (ix, frame) in key_dispatch_stack.iter().enumerate() {
|
|
||||||
match frame {
|
|
||||||
KeyDispatchStackFrame::Listener {
|
|
||||||
event_type,
|
|
||||||
listener,
|
|
||||||
} => {
|
|
||||||
if key_event_type == *event_type {
|
|
||||||
if let Some(action) = listener(
|
|
||||||
any_key_event,
|
|
||||||
&context_stack,
|
|
||||||
DispatchPhase::Capture,
|
|
||||||
self,
|
|
||||||
) {
|
|
||||||
self.dispatch_action_internal(action, &key_dispatch_stack[..ix]);
|
|
||||||
}
|
|
||||||
if !self.app.propagate_event {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyDispatchStackFrame::Context(context) => {
|
|
||||||
context_stack.push(&context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.app.propagate_event {
|
|
||||||
for (ix, frame) in key_dispatch_stack.iter().enumerate().rev() {
|
|
||||||
match frame {
|
|
||||||
KeyDispatchStackFrame::Listener {
|
|
||||||
event_type,
|
|
||||||
listener,
|
|
||||||
} => {
|
|
||||||
if key_event_type == *event_type {
|
|
||||||
if let Some(action) = listener(
|
|
||||||
any_key_event,
|
|
||||||
&context_stack,
|
|
||||||
DispatchPhase::Bubble,
|
|
||||||
self,
|
|
||||||
) {
|
|
||||||
self.dispatch_action_internal(
|
|
||||||
action,
|
|
||||||
&key_dispatch_stack[..ix],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.app.propagate_event {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyDispatchStackFrame::Context(_) => {
|
|
||||||
context_stack.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(context_stack);
|
|
||||||
self.window.current_frame.key_dispatch_stack = key_dispatch_stack;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
!self.app.propagate_event
|
!self.app.propagate_event
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to map a keystroke to an action based on the keymap.
|
fn dispatch_mouse_event(&mut self, event: &dyn Any) {
|
||||||
pub fn match_keystroke(
|
if let Some(mut handlers) = self
|
||||||
&mut self,
|
|
||||||
element_id: &GlobalElementId,
|
|
||||||
keystroke: &Keystroke,
|
|
||||||
context_stack: &[&DispatchContext],
|
|
||||||
) -> KeyMatch {
|
|
||||||
let key_match = self
|
|
||||||
.window
|
.window
|
||||||
.current_frame
|
.current_frame
|
||||||
.key_matchers
|
.mouse_listeners
|
||||||
.get_mut(element_id)
|
.remove(&event.type_id())
|
||||||
.unwrap()
|
{
|
||||||
.match_keystroke(keystroke, context_stack);
|
// Because handlers may add other handlers, we sort every time.
|
||||||
|
handlers.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||||
|
|
||||||
if key_match.is_some() {
|
// Capture phase, events bubble from back to front. Handlers for this phase are used for
|
||||||
for matcher in self.window.current_frame.key_matchers.values_mut() {
|
// special purposes, such as detecting events outside of a given Bounds.
|
||||||
matcher.clear_pending();
|
for (_, handler) in &mut handlers {
|
||||||
|
handler(event, DispatchPhase::Capture, self);
|
||||||
|
if !self.app.propagate_event {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bubble phase, where most normal handlers do their work.
|
||||||
|
if self.app.propagate_event {
|
||||||
|
for (_, handler) in handlers.iter_mut().rev() {
|
||||||
|
handler(event, DispatchPhase::Bubble, self);
|
||||||
|
if !self.app.propagate_event {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.app.propagate_event && event.downcast_ref::<MouseUpEvent>().is_some() {
|
||||||
|
self.active_drag = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just in case any handlers added new handlers, which is weird, but possible.
|
||||||
|
handlers.extend(
|
||||||
|
self.window
|
||||||
|
.current_frame
|
||||||
|
.mouse_listeners
|
||||||
|
.get_mut(&event.type_id())
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|handlers| handlers.drain(..)),
|
||||||
|
);
|
||||||
|
self.window
|
||||||
|
.current_frame
|
||||||
|
.mouse_listeners
|
||||||
|
.insert(event.type_id(), handlers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Capture phase
|
||||||
|
let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new();
|
||||||
|
self.propagate_event = true;
|
||||||
|
|
||||||
|
for node_id in &dispatch_path {
|
||||||
|
let node = self.window.current_frame.dispatch_tree.node(*node_id);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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::<KeyDownEvent>() {
|
||||||
|
if let Some(action) = self
|
||||||
|
.window
|
||||||
|
.current_frame
|
||||||
|
.dispatch_tree
|
||||||
|
.dispatch_key(&key_down_event.keystroke, &context_stack)
|
||||||
|
{
|
||||||
|
self.dispatch_action_on_node(*node_id, action);
|
||||||
|
if !self.propagate_event {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context_stack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box<dyn Action>) {
|
||||||
|
let dispatch_path = self
|
||||||
|
.window
|
||||||
|
.current_frame
|
||||||
|
.dispatch_tree
|
||||||
|
.dispatch_path(node_id);
|
||||||
|
|
||||||
|
// Capture phase
|
||||||
|
for node_id in &dispatch_path {
|
||||||
|
let node = self.window.current_frame.dispatch_tree.node(*node_id);
|
||||||
|
for DispatchActionListener {
|
||||||
|
action_type,
|
||||||
|
listener,
|
||||||
|
} in node.action_listeners.clone()
|
||||||
|
{
|
||||||
|
let any_action = action.as_any();
|
||||||
|
if action_type == any_action.type_id() {
|
||||||
|
listener(any_action, DispatchPhase::Capture, self);
|
||||||
|
if !self.propagate_event {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
key_match
|
// Bubble phase
|
||||||
|
for node_id in dispatch_path.iter().rev() {
|
||||||
|
let node = self.window.current_frame.dispatch_tree.node(*node_id);
|
||||||
|
for DispatchActionListener {
|
||||||
|
action_type,
|
||||||
|
listener,
|
||||||
|
} in node.action_listeners.clone()
|
||||||
|
{
|
||||||
|
let any_action = action.as_any();
|
||||||
|
if action_type == any_action.type_id() {
|
||||||
|
self.propagate_event = false; // Actions stop propagation by default during the bubble phase
|
||||||
|
listener(any_action, DispatchPhase::Bubble, self);
|
||||||
|
if !self.propagate_event {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register the given handler to be invoked whenever the global of the given type
|
/// Register the given handler to be invoked whenever the global of the given type
|
||||||
|
@ -1345,105 +1367,14 @@ impl<'a> WindowContext<'a> {
|
||||||
self.window.platform_window.prompt(level, msg, answers)
|
self.window.platform_window.prompt(level, msg, answers)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn available_actions(&self) -> impl Iterator<Item = Box<dyn Action>> + '_ {
|
pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
|
||||||
let key_dispatch_stack = &self.window.previous_frame.key_dispatch_stack;
|
if let Some(focus_id) = self.window.focus {
|
||||||
key_dispatch_stack.iter().filter_map(|frame| {
|
self.window
|
||||||
match frame {
|
.current_frame
|
||||||
// todo!factor out a KeyDispatchStackFrame::Action
|
.dispatch_tree
|
||||||
KeyDispatchStackFrame::Listener {
|
.available_actions(focus_id)
|
||||||
event_type,
|
} else {
|
||||||
listener: _,
|
Vec::new()
|
||||||
} => {
|
|
||||||
match build_action_from_type(event_type) {
|
|
||||||
Ok(action) => Some(action),
|
|
||||||
Err(err) => {
|
|
||||||
dbg!(err);
|
|
||||||
None
|
|
||||||
} // we'll hit his if TypeId == KeyDown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyDispatchStackFrame::Context(_) => None,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn dispatch_action_internal(
|
|
||||||
&mut self,
|
|
||||||
action: Box<dyn Action>,
|
|
||||||
dispatch_stack: &[KeyDispatchStackFrame],
|
|
||||||
) {
|
|
||||||
let action_type = action.as_any().type_id();
|
|
||||||
|
|
||||||
if let Some(mut global_listeners) = self.app.global_action_listeners.remove(&action_type) {
|
|
||||||
for listener in &global_listeners {
|
|
||||||
listener(action.as_ref(), DispatchPhase::Capture, self);
|
|
||||||
if !self.app.propagate_event {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
global_listeners.extend(
|
|
||||||
self.global_action_listeners
|
|
||||||
.remove(&action_type)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
);
|
|
||||||
self.global_action_listeners
|
|
||||||
.insert(action_type, global_listeners);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.app.propagate_event {
|
|
||||||
for stack_frame in dispatch_stack {
|
|
||||||
if let KeyDispatchStackFrame::Listener {
|
|
||||||
event_type,
|
|
||||||
listener,
|
|
||||||
} = stack_frame
|
|
||||||
{
|
|
||||||
if action_type == *event_type {
|
|
||||||
listener(action.as_any(), &[], DispatchPhase::Capture, self);
|
|
||||||
if !self.app.propagate_event {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.app.propagate_event {
|
|
||||||
for stack_frame in dispatch_stack.iter().rev() {
|
|
||||||
if let KeyDispatchStackFrame::Listener {
|
|
||||||
event_type,
|
|
||||||
listener,
|
|
||||||
} = stack_frame
|
|
||||||
{
|
|
||||||
if action_type == *event_type {
|
|
||||||
self.app.propagate_event = false;
|
|
||||||
listener(action.as_any(), &[], DispatchPhase::Bubble, self);
|
|
||||||
if !self.app.propagate_event {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.app.propagate_event {
|
|
||||||
if let Some(mut global_listeners) =
|
|
||||||
self.app.global_action_listeners.remove(&action_type)
|
|
||||||
{
|
|
||||||
for listener in global_listeners.iter().rev() {
|
|
||||||
self.app.propagate_event = false;
|
|
||||||
listener(action.as_ref(), DispatchPhase::Bubble, self);
|
|
||||||
if !self.app.propagate_event {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
global_listeners.extend(
|
|
||||||
self.global_action_listeners
|
|
||||||
.remove(&action_type)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
);
|
|
||||||
self.global_action_listeners
|
|
||||||
.insert(action_type, global_listeners);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1609,22 +1540,9 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
|
||||||
id: impl Into<ElementId>,
|
id: impl Into<ElementId>,
|
||||||
f: impl FnOnce(GlobalElementId, &mut Self) -> R,
|
f: impl FnOnce(GlobalElementId, &mut Self) -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
let keymap = self.app_mut().keymap.clone();
|
|
||||||
let window = self.window_mut();
|
let window = self.window_mut();
|
||||||
window.element_id_stack.push(id.into());
|
window.element_id_stack.push(id.into());
|
||||||
let global_id = window.element_id_stack.clone();
|
let global_id = window.element_id_stack.clone();
|
||||||
|
|
||||||
if window.current_frame.key_matchers.get(&global_id).is_none() {
|
|
||||||
window.current_frame.key_matchers.insert(
|
|
||||||
global_id.clone(),
|
|
||||||
window
|
|
||||||
.previous_frame
|
|
||||||
.key_matchers
|
|
||||||
.remove(&global_id)
|
|
||||||
.unwrap_or_else(|| KeyMatcher::new(keymap)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = f(global_id, self);
|
let result = f(global_id, self);
|
||||||
let window: &mut Window = self.borrow_mut();
|
let window: &mut Window = self.borrow_mut();
|
||||||
window.element_id_stack.pop();
|
window.element_id_stack.pop();
|
||||||
|
@ -2109,94 +2027,28 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_key_listeners<R>(
|
pub fn with_key_dispatch<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
key_listeners: impl IntoIterator<Item = (TypeId, KeyListener<V>)>,
|
context: KeyContext,
|
||||||
f: impl FnOnce(&mut Self) -> R,
|
focus_handle: Option<FocusHandle>,
|
||||||
|
f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
let old_stack_len = self.window.current_frame.key_dispatch_stack.len();
|
let window = &mut self.window;
|
||||||
if !self.window.current_frame.freeze_key_dispatch_stack {
|
|
||||||
for (event_type, listener) in key_listeners {
|
|
||||||
let handle = self.view().downgrade();
|
|
||||||
let listener = Box::new(
|
|
||||||
move |event: &dyn Any,
|
|
||||||
context_stack: &[&DispatchContext],
|
|
||||||
phase: DispatchPhase,
|
|
||||||
cx: &mut WindowContext<'_>| {
|
|
||||||
handle
|
|
||||||
.update(cx, |view, cx| {
|
|
||||||
listener(view, event, context_stack, phase, cx)
|
|
||||||
})
|
|
||||||
.log_err()
|
|
||||||
.flatten()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.window.current_frame.key_dispatch_stack.push(
|
|
||||||
KeyDispatchStackFrame::Listener {
|
|
||||||
event_type,
|
|
||||||
listener,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = f(self);
|
window
|
||||||
|
.current_frame
|
||||||
if !self.window.current_frame.freeze_key_dispatch_stack {
|
.dispatch_tree
|
||||||
self.window
|
.push_node(context, &mut window.previous_frame.dispatch_tree);
|
||||||
|
if let Some(focus_handle) = focus_handle.as_ref() {
|
||||||
|
window
|
||||||
.current_frame
|
.current_frame
|
||||||
.key_dispatch_stack
|
.dispatch_tree
|
||||||
.truncate(old_stack_len);
|
.make_focusable(focus_handle.id);
|
||||||
}
|
}
|
||||||
|
let result = f(focus_handle, self);
|
||||||
|
|
||||||
result
|
self.window.current_frame.dispatch_tree.pop_node();
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_key_dispatch_context<R>(
|
|
||||||
&mut self,
|
|
||||||
context: DispatchContext,
|
|
||||||
f: impl FnOnce(&mut Self) -> R,
|
|
||||||
) -> R {
|
|
||||||
if context.is_empty() {
|
|
||||||
return f(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.window.current_frame.freeze_key_dispatch_stack {
|
|
||||||
self.window
|
|
||||||
.current_frame
|
|
||||||
.key_dispatch_stack
|
|
||||||
.push(KeyDispatchStackFrame::Context(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = f(self);
|
|
||||||
|
|
||||||
if !self.window.previous_frame.freeze_key_dispatch_stack {
|
|
||||||
self.window.previous_frame.key_dispatch_stack.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_focus<R>(
|
|
||||||
&mut self,
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
f: impl FnOnce(&mut Self) -> R,
|
|
||||||
) -> R {
|
|
||||||
if let Some(parent_focus_id) = self.window.current_frame.focus_stack.last().copied() {
|
|
||||||
self.window
|
|
||||||
.current_frame
|
|
||||||
.focus_parents_by_child
|
|
||||||
.insert(focus_handle.id, parent_focus_id);
|
|
||||||
}
|
|
||||||
self.window.current_frame.focus_stack.push(focus_handle.id);
|
|
||||||
|
|
||||||
if Some(focus_handle.id) == self.window.focus {
|
|
||||||
self.window.current_frame.freeze_key_dispatch_stack = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = f(self);
|
|
||||||
|
|
||||||
self.window.current_frame.focus_stack.pop();
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2250,6 +2102,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
|
/// Set an input handler, such as [ElementInputHandler], which interfaces with the
|
||||||
/// platform to receive textual input with proper integration with concerns such
|
/// platform to receive textual input with proper integration with concerns such
|
||||||
/// as IME interactions.
|
/// as IME interactions.
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, uniform_list, Component, Div, FocusEnabled, ParentElement, Render, StatefulInteractivity,
|
div, uniform_list, Component, Div, ParentElement, Render, StatelessInteractive, Styled, Task,
|
||||||
StatelessInteractive, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
|
UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext,
|
||||||
WindowContext,
|
|
||||||
};
|
};
|
||||||
use std::{cmp, sync::Arc};
|
use std::{cmp, sync::Arc};
|
||||||
use ui::{prelude::*, v_stack, Divider, Label, LabelColor};
|
use ui::{prelude::*, v_stack, Divider, Label, LabelColor};
|
||||||
|
@ -140,13 +139,11 @@ impl<D: PickerDelegate> Picker<D> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: PickerDelegate> Render for Picker<D> {
|
impl<D: PickerDelegate> Render for Picker<D> {
|
||||||
type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
|
type Element = Div<Self>;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
div()
|
div()
|
||||||
.context("picker")
|
.context("picker")
|
||||||
.id("picker-container")
|
|
||||||
.focusable()
|
|
||||||
.size_full()
|
.size_full()
|
||||||
.elevation_2(cx)
|
.elevation_2(cx)
|
||||||
.on_action(Self::select_next)
|
.on_action(Self::select_next)
|
||||||
|
|
|
@ -9,7 +9,7 @@ use file_associations::FileAssociations;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, px, rems, svg, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
|
actions, div, px, rems, svg, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
|
||||||
ClipboardItem, Component, Div, Entity, EventEmitter, FocusEnabled, FocusHandle, Model,
|
ClipboardItem, Component, Div, Entity, EventEmitter, FocusHandle, FocusableKeyDispatch, Model,
|
||||||
ParentElement as _, Pixels, Point, PromptLevel, Render, StatefulInteractive,
|
ParentElement as _, Pixels, Point, PromptLevel, Render, StatefulInteractive,
|
||||||
StatefulInteractivity, StatelessInteractive, Styled, Task, UniformListScrollHandle, View,
|
StatefulInteractivity, StatelessInteractive, Styled, Task, UniformListScrollHandle, View,
|
||||||
ViewContext, VisualContext as _, WeakView, WindowContext,
|
ViewContext, VisualContext as _, WeakView, WindowContext,
|
||||||
|
@ -1431,7 +1431,7 @@ impl ProjectPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for ProjectPanel {
|
impl Render for ProjectPanel {
|
||||||
type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
|
type Element = Div<Self, StatefulInteractivity<Self>, FocusableKeyDispatch<Self>>;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render,
|
actions, div, Div, FocusHandle, Focusable, FocusableKeyDispatch, KeyBinding, ParentElement,
|
||||||
StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext, WindowContext,
|
Render, StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext,
|
||||||
|
WindowContext,
|
||||||
};
|
};
|
||||||
use theme2::ActiveTheme;
|
use theme2::ActiveTheme;
|
||||||
|
|
||||||
actions!(ActionA, ActionB, ActionC);
|
actions!(ActionA, ActionB, ActionC);
|
||||||
|
|
||||||
pub struct FocusStory {}
|
pub struct FocusStory {
|
||||||
|
child_1_focus: FocusHandle,
|
||||||
|
child_2_focus: FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
impl FocusStory {
|
impl FocusStory {
|
||||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||||
|
@ -16,12 +20,15 @@ impl FocusStory {
|
||||||
KeyBinding::new("cmd-c", ActionC, None),
|
KeyBinding::new("cmd-c", ActionC, None),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
cx.build_view(move |cx| Self {})
|
cx.build_view(move |cx| Self {
|
||||||
|
child_1_focus: cx.focus_handle(),
|
||||||
|
child_2_focus: cx.focus_handle(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for FocusStory {
|
impl Render for FocusStory {
|
||||||
type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
|
type Element = Div<Self, StatefulInteractivity<Self>, FocusableKeyDispatch<Self>>;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
|
@ -31,18 +38,16 @@ impl Render for FocusStory {
|
||||||
let color_4 = theme.status().conflict;
|
let color_4 = theme.status().conflict;
|
||||||
let color_5 = theme.status().ignored;
|
let color_5 = theme.status().ignored;
|
||||||
let color_6 = theme.status().renamed;
|
let color_6 = theme.status().renamed;
|
||||||
let child_1 = cx.focus_handle();
|
|
||||||
let child_2 = cx.focus_handle();
|
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id("parent")
|
.id("parent")
|
||||||
.focusable()
|
.focusable()
|
||||||
.context("parent")
|
.context("parent")
|
||||||
.on_action(|_, action: &ActionA, cx| {
|
.on_action(|_, action: &ActionA, cx| {
|
||||||
println!("Action A dispatched on parent during");
|
println!("Action A dispatched on parent");
|
||||||
})
|
})
|
||||||
.on_action(|_, action: &ActionB, cx| {
|
.on_action(|_, action: &ActionB, cx| {
|
||||||
println!("Action B dispatched on parent during");
|
println!("Action B dispatched on parent");
|
||||||
})
|
})
|
||||||
.on_focus(|_, _, _| println!("Parent focused"))
|
.on_focus(|_, _, _| println!("Parent focused"))
|
||||||
.on_blur(|_, _, _| println!("Parent blurred"))
|
.on_blur(|_, _, _| println!("Parent blurred"))
|
||||||
|
@ -56,7 +61,7 @@ impl Render for FocusStory {
|
||||||
.focus_in(|style| style.bg(color_3))
|
.focus_in(|style| style.bg(color_3))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.track_focus(&child_1)
|
.track_focus(&self.child_1_focus)
|
||||||
.context("child-1")
|
.context("child-1")
|
||||||
.on_action(|_, action: &ActionB, cx| {
|
.on_action(|_, action: &ActionB, cx| {
|
||||||
println!("Action B dispatched on child 1 during");
|
println!("Action B dispatched on child 1 during");
|
||||||
|
@ -76,10 +81,10 @@ impl Render for FocusStory {
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.track_focus(&child_2)
|
.track_focus(&self.child_2_focus)
|
||||||
.context("child-2")
|
.context("child-2")
|
||||||
.on_action(|_, action: &ActionC, cx| {
|
.on_action(|_, action: &ActionC, cx| {
|
||||||
println!("Action C dispatched on child 2 during");
|
println!("Action C dispatched on child 2");
|
||||||
})
|
})
|
||||||
.w_full()
|
.w_full()
|
||||||
.h_6()
|
.h_6()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use gpui::{Div, ElementFocus, ElementInteractivity, Styled, UniformList, ViewContext};
|
use gpui::{Div, ElementInteractivity, KeyDispatch, Styled, UniformList, ViewContext};
|
||||||
use theme2::ActiveTheme;
|
use theme2::ActiveTheme;
|
||||||
|
|
||||||
use crate::{ElevationIndex, UITextSize};
|
use crate::{ElevationIndex, UITextSize};
|
||||||
|
@ -96,7 +96,7 @@ pub trait StyledExt: Styled + Sized {
|
||||||
impl<V, I, F> StyledExt for Div<V, I, F>
|
impl<V, I, F> StyledExt for Div<V, I, F>
|
||||||
where
|
where
|
||||||
I: ElementInteractivity<V>,
|
I: ElementInteractivity<V>,
|
||||||
F: ElementFocus<V>,
|
F: KeyDispatch<V>,
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,10 +37,10 @@ use futures::{
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, point, rems, size, Action, AnyModel, AnyView, AnyWeakView, AppContext,
|
actions, div, point, rems, size, Action, AnyModel, AnyView, AnyWeakView, AppContext,
|
||||||
AsyncAppContext, AsyncWindowContext, Bounds, Component, DispatchContext, Div, Entity, EntityId,
|
AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter,
|
||||||
EventEmitter, FocusHandle, GlobalPixels, Model, ModelContext, ParentElement, Point, Render,
|
FocusHandle, GlobalPixels, KeyContext, Model, ModelContext, ParentElement, Point, Render, Size,
|
||||||
Size, StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled, Subscription,
|
StatefulInteractive, StatelessInteractive, StatelessInteractivity, Styled, Subscription, Task,
|
||||||
Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
|
View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
|
||||||
WindowOptions,
|
WindowOptions,
|
||||||
};
|
};
|
||||||
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
|
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
|
||||||
|
@ -438,8 +438,8 @@ pub struct Workspace {
|
||||||
workspace_actions: Vec<
|
workspace_actions: Vec<
|
||||||
Box<
|
Box<
|
||||||
dyn Fn(
|
dyn Fn(
|
||||||
Div<Workspace, StatefulInteractivity<Workspace>>,
|
Div<Workspace, StatelessInteractivity<Workspace>>,
|
||||||
) -> Div<Workspace, StatefulInteractivity<Workspace>>,
|
) -> Div<Workspace, StatelessInteractivity<Workspace>>,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
zoomed: Option<AnyWeakView>,
|
zoomed: Option<AnyWeakView>,
|
||||||
|
@ -3427,8 +3427,8 @@ impl Workspace {
|
||||||
|
|
||||||
fn add_workspace_actions_listeners(
|
fn add_workspace_actions_listeners(
|
||||||
&self,
|
&self,
|
||||||
mut div: Div<Workspace, StatefulInteractivity<Workspace>>,
|
mut div: Div<Workspace, StatelessInteractivity<Workspace>>,
|
||||||
) -> Div<Workspace, StatefulInteractivity<Workspace>> {
|
) -> Div<Workspace, StatelessInteractivity<Workspace>> {
|
||||||
for action in self.workspace_actions.iter() {
|
for action in self.workspace_actions.iter() {
|
||||||
div = (action)(div)
|
div = (action)(div)
|
||||||
}
|
}
|
||||||
|
@ -3656,108 +3656,107 @@ impl Render for Workspace {
|
||||||
type Element = Div<Self>;
|
type Element = Div<Self>;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
let mut context = DispatchContext::default();
|
let mut context = KeyContext::default();
|
||||||
context.insert("Workspace");
|
context.add("Workspace");
|
||||||
cx.with_key_dispatch_context(context, |cx| {
|
|
||||||
div()
|
self.add_workspace_actions_listeners(div())
|
||||||
.relative()
|
.context(context)
|
||||||
.size_full()
|
.relative()
|
||||||
.flex()
|
.size_full()
|
||||||
.flex_col()
|
.flex()
|
||||||
.font("Zed Sans")
|
.flex_col()
|
||||||
.gap_0()
|
.font("Zed Sans")
|
||||||
.justify_start()
|
.gap_0()
|
||||||
.items_start()
|
.justify_start()
|
||||||
.text_color(cx.theme().colors().text)
|
.items_start()
|
||||||
.bg(cx.theme().colors().background)
|
.text_color(cx.theme().colors().text)
|
||||||
.child(self.render_titlebar(cx))
|
.bg(cx.theme().colors().background)
|
||||||
.child(
|
.child(self.render_titlebar(cx))
|
||||||
// todo! should this be a component a view?
|
.child(
|
||||||
self.add_workspace_actions_listeners(div().id("workspace"))
|
// todo! should this be a component a view?
|
||||||
.relative()
|
div()
|
||||||
.flex_1()
|
.id("workspace")
|
||||||
.w_full()
|
.relative()
|
||||||
.flex()
|
.flex_1()
|
||||||
.overflow_hidden()
|
.w_full()
|
||||||
.border_t()
|
.flex()
|
||||||
.border_b()
|
.overflow_hidden()
|
||||||
.border_color(cx.theme().colors().border)
|
.border_t()
|
||||||
.child(self.modal_layer.clone())
|
.border_b()
|
||||||
.child(
|
.border_color(cx.theme().colors().border)
|
||||||
div()
|
.child(self.modal_layer.clone())
|
||||||
.flex()
|
.child(
|
||||||
.flex_row()
|
div()
|
||||||
.flex_1()
|
.flex()
|
||||||
.h_full()
|
.flex_row()
|
||||||
.child(div().flex().flex_1().child(self.left_dock.clone()))
|
.flex_1()
|
||||||
.child(
|
.h_full()
|
||||||
div()
|
.child(div().flex().flex_1().child(self.left_dock.clone()))
|
||||||
.flex()
|
.child(
|
||||||
.flex_col()
|
div()
|
||||||
.flex_1()
|
.flex()
|
||||||
.child(self.center.render(
|
.flex_col()
|
||||||
&self.project,
|
.flex_1()
|
||||||
&self.follower_states,
|
.child(self.center.render(
|
||||||
self.active_call(),
|
&self.project,
|
||||||
&self.active_pane,
|
&self.follower_states,
|
||||||
self.zoomed.as_ref(),
|
self.active_call(),
|
||||||
&self.app_state,
|
&self.active_pane,
|
||||||
cx,
|
self.zoomed.as_ref(),
|
||||||
))
|
&self.app_state,
|
||||||
.child(
|
cx,
|
||||||
div().flex().flex_1().child(self.bottom_dock.clone()),
|
))
|
||||||
),
|
.child(div().flex().flex_1().child(self.bottom_dock.clone())),
|
||||||
)
|
)
|
||||||
.child(div().flex().flex_1().child(self.right_dock.clone())),
|
.child(div().flex().flex_1().child(self.right_dock.clone())),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(self.status_bar.clone())
|
.child(self.status_bar.clone())
|
||||||
// .when(self.debug.show_toast, |this| {
|
// .when(self.debug.show_toast, |this| {
|
||||||
// this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
|
// this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
|
||||||
// })
|
// })
|
||||||
// .children(
|
// .children(
|
||||||
// Some(
|
// Some(
|
||||||
// div()
|
// div()
|
||||||
// .absolute()
|
// .absolute()
|
||||||
// .top(px(50.))
|
// .top(px(50.))
|
||||||
// .left(px(640.))
|
// .left(px(640.))
|
||||||
// .z_index(8)
|
// .z_index(8)
|
||||||
// .child(LanguageSelector::new("language-selector")),
|
// .child(LanguageSelector::new("language-selector")),
|
||||||
// )
|
// )
|
||||||
// .filter(|_| self.is_language_selector_open()),
|
// .filter(|_| self.is_language_selector_open()),
|
||||||
// )
|
// )
|
||||||
.z_index(8)
|
.z_index(8)
|
||||||
// Debug
|
// Debug
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.z_index(9)
|
.z_index(9)
|
||||||
.absolute()
|
.absolute()
|
||||||
.top_20()
|
.top_20()
|
||||||
.left_1_4()
|
.left_1_4()
|
||||||
.w_40()
|
.w_40()
|
||||||
.gap_2(), // .when(self.show_debug, |this| {
|
.gap_2(), // .when(self.show_debug, |this| {
|
||||||
// this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
|
// this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
|
||||||
// Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
|
// Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
|
||||||
// ))
|
// ))
|
||||||
// .child(
|
// .child(
|
||||||
// Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
|
// Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
|
||||||
// |workspace, cx| workspace.debug_toggle_toast(cx),
|
// |workspace, cx| workspace.debug_toggle_toast(cx),
|
||||||
// )),
|
// )),
|
||||||
// )
|
// )
|
||||||
// .child(
|
// .child(
|
||||||
// Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
|
// Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
|
||||||
// |workspace, cx| workspace.debug_toggle_livestream(cx),
|
// |workspace, cx| workspace.debug_toggle_livestream(cx),
|
||||||
// )),
|
// )),
|
||||||
// )
|
// )
|
||||||
// })
|
// })
|
||||||
// .child(
|
// .child(
|
||||||
// Button::<Workspace>::new("Toggle Debug")
|
// Button::<Workspace>::new("Toggle Debug")
|
||||||
// .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
|
// .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
|
||||||
// ),
|
// ),
|
||||||
)
|
)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// todo!()
|
// todo!()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue