Detailed changes
@@ -41,8 +41,8 @@ use git::diff_hunk_to_display;
use gpui::{
action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext,
AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
- DispatchContext, EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight,
- HighlightStyle, Hsla, InputHandler, Model, MouseButton, ParentElement, Pixels, Render,
+ EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla,
+ InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render,
StatelessInteractive, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
ViewContext, VisualContext, WeakView, WindowContext,
};
@@ -646,7 +646,7 @@ pub struct Editor {
collapse_matches: bool,
autoindent_mode: Option<AutoindentMode>,
workspace: Option<(WeakView<Workspace>, i64)>,
- keymap_context_layers: BTreeMap<TypeId, DispatchContext>,
+ keymap_context_layers: BTreeMap<TypeId, KeyContext>,
input_enabled: bool,
read_only: bool,
leader_peer_id: Option<PeerId>,
@@ -1981,9 +1981,9 @@ impl Editor {
this
}
- fn dispatch_context(&self, cx: &AppContext) -> DispatchContext {
- let mut dispatch_context = DispatchContext::default();
- dispatch_context.insert("Editor");
+ fn dispatch_context(&self, cx: &AppContext) -> KeyContext {
+ let mut dispatch_context = KeyContext::default();
+ dispatch_context.add("Editor");
let mode = match self.mode {
EditorMode::SingleLine => "single_line",
EditorMode::AutoHeight { .. } => "auto_height",
@@ -1991,17 +1991,17 @@ impl Editor {
};
dispatch_context.set("mode", mode);
if self.pending_rename.is_some() {
- dispatch_context.insert("renaming");
+ dispatch_context.add("renaming");
}
if self.context_menu_visible() {
match self.context_menu.read().as_ref() {
Some(ContextMenu::Completions(_)) => {
- dispatch_context.insert("menu");
- dispatch_context.insert("showing_completions")
+ dispatch_context.add("menu");
+ dispatch_context.add("showing_completions")
}
Some(ContextMenu::CodeActions(_)) => {
- dispatch_context.insert("menu");
- dispatch_context.insert("showing_code_actions")
+ dispatch_context.add("menu");
+ dispatch_context.add("showing_code_actions")
}
None => {}
}
@@ -16,11 +16,11 @@ use anyhow::Result;
use collections::{BTreeMap, HashMap};
use gpui::{
black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
- BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase,
- Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla,
- InputHandler, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton,
- MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size,
- Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout,
+ BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element,
+ ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler,
+ KeyContext, KeyDownEvent, KeyMatch, Line, LineLayout, Modifiers, MouseButton, MouseDownEvent,
+ MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size, Style, TextRun,
+ TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@@ -2456,11 +2456,170 @@ impl Element<Editor> for EditorElement {
let dispatch_context = editor.dispatch_context(cx);
cx.with_element_id(cx.view().entity_id(), |global_id, cx| {
- cx.with_key_dispatch_context(dispatch_context, |cx| {
- cx.with_key_listeners(build_key_listeners(global_id), |cx| {
- cx.with_focus(editor.focus_handle.clone(), |_| {})
- });
- })
+ cx.with_key_dispatch(
+ dispatch_context,
+ 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(
- global_element_id: GlobalElementId,
-) -> impl IntoIterator<Item = (TypeId, KeyListener<Editor>)> {
- [
- build_action_listener(Editor::move_left),
- build_action_listener(Editor::move_right),
- build_action_listener(Editor::move_down),
- build_action_listener(Editor::move_up),
- // build_action_listener(Editor::new_file), todo!()
- // build_action_listener(Editor::new_file_in_direction), todo!()
- build_action_listener(Editor::cancel),
- build_action_listener(Editor::newline),
- build_action_listener(Editor::newline_above),
- build_action_listener(Editor::newline_below),
- build_action_listener(Editor::backspace),
- build_action_listener(Editor::delete),
- build_action_listener(Editor::tab),
- build_action_listener(Editor::tab_prev),
- build_action_listener(Editor::indent),
- build_action_listener(Editor::outdent),
- build_action_listener(Editor::delete_line),
- build_action_listener(Editor::join_lines),
- build_action_listener(Editor::sort_lines_case_sensitive),
- build_action_listener(Editor::sort_lines_case_insensitive),
- build_action_listener(Editor::reverse_lines),
- build_action_listener(Editor::shuffle_lines),
- build_action_listener(Editor::convert_to_upper_case),
- build_action_listener(Editor::convert_to_lower_case),
- build_action_listener(Editor::convert_to_title_case),
- build_action_listener(Editor::convert_to_snake_case),
- build_action_listener(Editor::convert_to_kebab_case),
- build_action_listener(Editor::convert_to_upper_camel_case),
- build_action_listener(Editor::convert_to_lower_camel_case),
- build_action_listener(Editor::delete_to_previous_word_start),
- build_action_listener(Editor::delete_to_previous_subword_start),
- build_action_listener(Editor::delete_to_next_word_end),
- build_action_listener(Editor::delete_to_next_subword_end),
- build_action_listener(Editor::delete_to_beginning_of_line),
- build_action_listener(Editor::delete_to_end_of_line),
- build_action_listener(Editor::cut_to_end_of_line),
- build_action_listener(Editor::duplicate_line),
- build_action_listener(Editor::move_line_up),
- build_action_listener(Editor::move_line_down),
- build_action_listener(Editor::transpose),
- build_action_listener(Editor::cut),
- build_action_listener(Editor::copy),
- build_action_listener(Editor::paste),
- build_action_listener(Editor::undo),
- build_action_listener(Editor::redo),
- build_action_listener(Editor::move_page_up),
- build_action_listener(Editor::move_page_down),
- build_action_listener(Editor::next_screen),
- build_action_listener(Editor::scroll_cursor_top),
- build_action_listener(Editor::scroll_cursor_center),
- build_action_listener(Editor::scroll_cursor_bottom),
- build_action_listener(|editor, _: &LineDown, cx| {
- editor.scroll_screen(&ScrollAmount::Line(1.), cx)
- }),
- build_action_listener(|editor, _: &LineUp, cx| {
- editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
- }),
- build_action_listener(|editor, _: &HalfPageDown, cx| {
- editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
- }),
- build_action_listener(|editor, _: &HalfPageUp, cx| {
- editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
- }),
- build_action_listener(|editor, _: &PageDown, cx| {
- editor.scroll_screen(&ScrollAmount::Page(1.), cx)
- }),
- build_action_listener(|editor, _: &PageUp, cx| {
- editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
- }),
- build_action_listener(Editor::move_to_previous_word_start),
- build_action_listener(Editor::move_to_previous_subword_start),
- build_action_listener(Editor::move_to_next_word_end),
- build_action_listener(Editor::move_to_next_subword_end),
- build_action_listener(Editor::move_to_beginning_of_line),
- build_action_listener(Editor::move_to_end_of_line),
- build_action_listener(Editor::move_to_start_of_paragraph),
- build_action_listener(Editor::move_to_end_of_paragraph),
- build_action_listener(Editor::move_to_beginning),
- build_action_listener(Editor::move_to_end),
- build_action_listener(Editor::select_up),
- build_action_listener(Editor::select_down),
- build_action_listener(Editor::select_left),
- build_action_listener(Editor::select_right),
- build_action_listener(Editor::select_to_previous_word_start),
- build_action_listener(Editor::select_to_previous_subword_start),
- build_action_listener(Editor::select_to_next_word_end),
- build_action_listener(Editor::select_to_next_subword_end),
- build_action_listener(Editor::select_to_beginning_of_line),
- build_action_listener(Editor::select_to_end_of_line),
- build_action_listener(Editor::select_to_start_of_paragraph),
- build_action_listener(Editor::select_to_end_of_paragraph),
- build_action_listener(Editor::select_to_beginning),
- build_action_listener(Editor::select_to_end),
- build_action_listener(Editor::select_all),
- build_action_listener(|editor, action, cx| {
- editor.select_all_matches(action, cx).log_err();
- }),
- build_action_listener(Editor::select_line),
- build_action_listener(Editor::split_selection_into_lines),
- build_action_listener(Editor::add_selection_above),
- build_action_listener(Editor::add_selection_below),
- build_action_listener(|editor, action, cx| {
- editor.select_next(action, cx).log_err();
- }),
- build_action_listener(|editor, action, cx| {
- editor.select_previous(action, cx).log_err();
- }),
- build_action_listener(Editor::toggle_comments),
- build_action_listener(Editor::select_larger_syntax_node),
- build_action_listener(Editor::select_smaller_syntax_node),
- build_action_listener(Editor::move_to_enclosing_bracket),
- build_action_listener(Editor::undo_selection),
- build_action_listener(Editor::redo_selection),
- build_action_listener(Editor::go_to_diagnostic),
- build_action_listener(Editor::go_to_prev_diagnostic),
- build_action_listener(Editor::go_to_hunk),
- build_action_listener(Editor::go_to_prev_hunk),
- build_action_listener(Editor::go_to_definition),
- build_action_listener(Editor::go_to_definition_split),
- build_action_listener(Editor::go_to_type_definition),
- build_action_listener(Editor::go_to_type_definition_split),
- build_action_listener(Editor::fold),
- build_action_listener(Editor::fold_at),
- build_action_listener(Editor::unfold_lines),
- build_action_listener(Editor::unfold_at),
- build_action_listener(Editor::fold_selected_ranges),
- build_action_listener(Editor::show_completions),
- build_action_listener(Editor::toggle_code_actions),
- // build_action_listener(Editor::open_excerpts), todo!()
- build_action_listener(Editor::toggle_soft_wrap),
- build_action_listener(Editor::toggle_inlay_hints),
- build_action_listener(Editor::reveal_in_finder),
- build_action_listener(Editor::copy_path),
- build_action_listener(Editor::copy_relative_path),
- build_action_listener(Editor::copy_highlight_json),
- build_action_listener(|editor, action, cx| {
- editor
- .format(action, cx)
- .map(|task| task.detach_and_log_err(cx));
- }),
- build_action_listener(Editor::restart_language_server),
- build_action_listener(Editor::show_character_palette),
- // build_action_listener(Editor::confirm_completion), todo!()
- build_action_listener(|editor, action, cx| {
- editor
- .confirm_code_action(action, cx)
- .map(|task| task.detach_and_log_err(cx));
- }),
- // build_action_listener(Editor::rename), todo!()
- // build_action_listener(Editor::confirm_rename), todo!()
- // build_action_listener(Editor::find_all_references), todo!()
- build_action_listener(Editor::next_copilot_suggestion),
- build_action_listener(Editor::previous_copilot_suggestion),
- build_action_listener(Editor::copilot_suggest),
- build_action_listener(Editor::context_menu_first),
- build_action_listener(Editor::context_menu_prev),
- build_action_listener(Editor::context_menu_next),
- build_action_listener(Editor::context_menu_last),
- 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>(
+fn register_action<T: Action>(
+ cx: &mut ViewContext<Editor>,
listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
-) -> (TypeId, KeyListener<Editor>) {
- build_key_listener(move |editor, action: &T, dispatch_context, phase, cx| {
+) {
+ cx.on_action(TypeId::of::<T>(), move |editor, action, phase, cx| {
+ let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
listener(editor, action, cx);
}
- None
})
}
@@ -1,8 +1,7 @@
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
use gpui::{
actions, div, AppContext, Div, EventEmitter, ParentElement, Render, SharedString,
- StatefulInteractivity, StatelessInteractive, Styled, Subscription, View, ViewContext,
- VisualContext, WindowContext,
+ StatelessInteractive, Styled, Subscription, View, ViewContext, VisualContext, WindowContext,
};
use text::{Bias, Point};
use theme::ActiveTheme;
@@ -146,11 +145,11 @@ impl 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 {
modal(cx)
- .id("go to line")
+ .context("GoToLine")
.on_action(Self::cancel)
.on_action(Self::confirm)
.w_96()
@@ -0,0 +1 @@
+
@@ -1,6 +1,6 @@
use crate::SharedString;
use anyhow::{anyhow, Context, Result};
-use collections::{HashMap, HashSet};
+use collections::HashMap;
use lazy_static::lazy_static;
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use serde::Deserialize;
@@ -186,401 +186,3 @@ macro_rules! actions {
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.
// We only honor effects for the most recently focused handle.
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
.map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap());
- let blurred = cx
- .window
- .last_blur
- .take()
- .unwrap()
- .and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles));
+
+ let blurred =
+ last_blur.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles));
+
let focus_changed = focused.is_some() || blurred.is_some();
let event = FocusEvent { focused, blurred };
@@ -1,29 +1,33 @@
+use std::fmt::Debug;
+
use crate::{
- point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId,
- ElementInteractivity, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
- GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
- Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
- StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext, Visibility,
+ point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, ElementInteractivity,
+ FocusHandle, FocusListeners, Focusable, FocusableKeyDispatch, GlobalElementId, GroupBounds,
+ InteractiveElementState, KeyContext, KeyDispatch, LayoutId, NonFocusableKeyDispatch, Overflow,
+ ParentElement, Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity,
+ StatelessInteractive, StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext,
+ Visibility,
};
use refineable::Refineable;
use smallvec::SmallVec;
+use util::ResultExt;
pub struct Div<
V: 'static,
I: ElementInteractivity<V> = StatelessInteractivity<V>,
- F: ElementFocus<V> = FocusDisabled,
+ K: KeyDispatch<V> = NonFocusableKeyDispatch,
> {
interactivity: I,
- focus: F,
+ key_dispatch: K,
children: SmallVec<[AnyElement<V>; 2]>,
group: Option<SharedString>,
base_style: StyleRefinement,
}
-pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, FocusDisabled> {
+pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
Div {
interactivity: StatelessInteractivity::default(),
- focus: FocusDisabled,
+ key_dispatch: NonFocusableKeyDispatch::default(),
children: SmallVec::new(),
group: None,
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>
where
V: 'static,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteractivity<V>, F> {
Div {
interactivity: StatefulInteractivity::new(id.into(), self.interactivity),
- focus: self.focus,
+ key_dispatch: self.key_dispatch,
children: self.children,
group: self.group,
base_style: self.base_style,
@@ -49,7 +53,7 @@ where
impl<V, I, F> Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
pub fn group(mut self, group: impl Into<SharedString>) -> Self {
self.group = Some(group.into());
@@ -61,6 +65,18 @@ where
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 {
self.base_style.overflow.x = Some(Overflow::Hidden);
self.base_style.overflow.y = Some(Overflow::Hidden);
@@ -97,7 +113,7 @@ where
) -> Style {
let mut computed_style = Style::default();
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(
&mut computed_style,
bounds,
@@ -108,11 +124,11 @@ where
}
}
-impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
- pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
+impl<V: 'static> Div<V, StatefulInteractivity<V>, NonFocusableKeyDispatch> {
+ pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
Div {
interactivity: self.interactivity,
- focus: FocusEnabled::new(),
+ key_dispatch: FocusableKeyDispatch::new(self.key_dispatch),
children: self.children,
group: self.group,
base_style: self.base_style,
@@ -122,10 +138,10 @@ impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
pub fn track_focus(
self,
handle: &FocusHandle,
- ) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
+ ) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
Div {
interactivity: self.interactivity,
- focus: FocusEnabled::tracked(handle),
+ key_dispatch: FocusableKeyDispatch::tracked(self.key_dispatch, handle),
children: self.children,
group: self.group,
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(
self,
handle: &FocusHandle,
- ) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
+ ) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
Div {
interactivity: self.interactivity.into_stateful(handle),
- focus: handle.clone().into(),
+ key_dispatch: FocusableKeyDispatch::tracked(self.key_dispatch, handle),
children: self.children,
group: self.group,
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
V: 'static,
I: ElementInteractivity<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) {
- self.focus.focus_style = style;
+ self.key_dispatch.focus_style = style;
}
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) {
- 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>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
type ElementState = DivState;
@@ -213,14 +229,18 @@ where
cx: &mut ViewContext<V>,
) -> Self::ElementState {
let mut element_state = element_state.unwrap_or_default();
- self.interactivity.initialize(cx, |cx| {
- self.focus
- .initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
+ self.with_element_id(cx, |this, _global_id, cx| {
+ this.key_dispatch.initialize(
+ element_state.focus_handle.take(),
+ cx,
+ |focus_handle, cx| {
+ this.interactivity.initialize(cx);
element_state.focus_handle = focus_handle;
- for child in &mut self.children {
+ for child in &mut this.children {
child.initialize(view_state, cx);
}
- })
+ },
+ );
});
element_state
}
@@ -288,7 +308,7 @@ where
cx.with_z_index(z_index, |cx| {
cx.with_z_index(0, |cx| {
style.paint(bounds, cx);
- this.focus.paint(bounds, cx);
+ this.key_dispatch.paint(bounds, cx);
this.interactivity.paint(
bounds,
content_size,
@@ -321,7 +341,7 @@ where
impl<V, I, F> Component<V> for Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
@@ -331,7 +351,7 @@ where
impl<V, I, F> ParentElement<V> for Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
@@ -341,7 +361,7 @@ where
impl<V, I, F> Styled for Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
&mut self.base_style
@@ -351,7 +371,7 @@ where
impl<V, I, F> StatelessInteractive<V> for Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.interactivity.as_stateless_mut()
@@ -360,7 +380,7 @@ where
impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteractivity<V>, F>
where
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
&mut self.interactivity
@@ -1,7 +1,7 @@
use crate::{
- div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus,
- ElementId, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
- LayoutId, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
+ div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementId,
+ ElementInteractivity, FocusListeners, Focusable, FocusableKeyDispatch, KeyDispatch, LayoutId,
+ NonFocusableKeyDispatch, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
};
use futures::FutureExt;
@@ -10,14 +10,14 @@ use util::ResultExt;
pub struct Img<
V: 'static,
I: ElementInteractivity<V> = StatelessInteractivity<V>,
- F: ElementFocus<V> = FocusDisabled,
+ F: KeyDispatch<V> = NonFocusableKeyDispatch,
> {
base: Div<V, I, F>,
uri: Option<SharedString>,
grayscale: bool,
}
-pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, FocusDisabled> {
+pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
Img {
base: div(),
uri: None,
@@ -29,7 +29,7 @@ impl<V, I, F> Img<V, I, F>
where
V: 'static,
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
self.uri = Some(uri.into());
@@ -44,7 +44,7 @@ where
impl<V, F> Img<V, StatelessInteractivity<V>, F>
where
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteractivity<V>, F> {
Img {
@@ -58,7 +58,7 @@ where
impl<V, I, F> Component<V> for Img<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
@@ -68,7 +68,7 @@ where
impl<V, I, F> Element<V> for Img<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
type ElementState = DivState;
@@ -137,7 +137,7 @@ where
impl<V, I, F> Styled for Img<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
@@ -147,7 +147,7 @@ where
impl<V, I, F> StatelessInteractive<V> for Img<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.base.stateless_interactivity()
@@ -156,14 +156,14 @@ where
impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteractivity<V>, F>
where
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
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
V: 'static,
I: ElementInteractivity<V>,
@@ -1,21 +1,21 @@
use crate::{
- div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId,
- ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
- SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
- StatelessInteractivity, StyleRefinement, Styled, ViewContext,
+ div, AnyElement, Bounds, Component, Div, DivState, Element, ElementId, ElementInteractivity,
+ FocusListeners, Focusable, FocusableKeyDispatch, KeyDispatch, LayoutId,
+ NonFocusableKeyDispatch, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
+ StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
};
use util::ResultExt;
pub struct Svg<
V: 'static,
I: ElementInteractivity<V> = StatelessInteractivity<V>,
- F: ElementFocus<V> = FocusDisabled,
+ F: KeyDispatch<V> = NonFocusableKeyDispatch,
> {
base: Div<V, I, F>,
path: Option<SharedString>,
}
-pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
+pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
Svg {
base: div(),
path: None,
@@ -25,7 +25,7 @@ pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
impl<V, I, F> Svg<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
self.path = Some(path.into());
@@ -35,7 +35,7 @@ where
impl<V, F> Svg<V, StatelessInteractivity<V>, F>
where
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteractivity<V>, F> {
Svg {
@@ -48,7 +48,7 @@ where
impl<V, I, F> Component<V> for Svg<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
@@ -58,7 +58,7 @@ where
impl<V, I, F> Element<V> for Svg<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
type ElementState = DivState;
@@ -108,7 +108,7 @@ where
impl<V, I, F> Styled for Svg<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
@@ -118,7 +118,7 @@ where
impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.base.stateless_interactivity()
@@ -128,14 +128,14 @@ where
impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteractivity<V>, F>
where
V: 'static,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
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
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 elements;
mod executor;
-mod focusable;
mod geometry;
mod image_cache;
mod input;
mod interactive;
+mod key_dispatch;
mod keymap;
mod platform;
+pub mod prelude;
mod scene;
mod style;
mod styled;
@@ -41,12 +42,12 @@ pub use ctor::ctor;
pub use element::*;
pub use elements::*;
pub use executor::*;
-pub use focusable::*;
pub use geometry::*;
pub use gpui2_macros::*;
pub use image_cache::*;
pub use input::*;
pub use interactive::*;
+pub use key_dispatch::*;
pub use keymap::*;
pub use platform::*;
use private::Sealed;
@@ -1,8 +1,8 @@
use crate::{
- div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds,
- Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch,
- Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style,
- StyleRefinement, Task, View, ViewContext,
+ div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, Bounds, Component,
+ DispatchPhase, Div, Element, ElementId, FocusHandle, KeyContext, Keystroke, Modifiers,
+ Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, Task, View,
+ ViewContext,
};
use collections::HashMap;
use derive_more::{Deref, DerefMut};
@@ -164,55 +164,41 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
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
- fn capture_action<A: 'static>(
+ fn capture_action<A: Action>(
mut self,
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
- self.stateless_interactivity().key_listeners.push((
+ self.stateless_interactivity().action_listeners.push((
TypeId::of::<A>(),
- Box::new(move |view, action, _dipatch_context, phase, cx| {
+ Box::new(move |view, action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Capture {
listener(view, action, cx)
}
- None
}),
));
self
}
/// Add a listener for the given action, fires during the bubble event phase
- fn on_action<A: 'static>(
+ fn on_action<A: Action>(
mut self,
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
- self.stateless_interactivity().key_listeners.push((
+ self.stateless_interactivity().action_listeners.push((
TypeId::of::<A>(),
- Box::new(move |view, action, _dispatch_context, phase, cx| {
+ Box::new(move |view, action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
listener(view, action, cx)
}
-
- None
}),
));
self
@@ -225,14 +211,11 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interactivity().key_listeners.push((
- TypeId::of::<KeyDownEvent>(),
- Box::new(move |view, event, _, phase, cx| {
- let event = event.downcast_ref().unwrap();
- listener(view, event, phase, cx);
- None
- }),
- ));
+ self.stateless_interactivity()
+ .key_down_listeners
+ .push(Box::new(move |view, event, phase, cx| {
+ listener(view, event, phase, cx)
+ }));
self
}
@@ -243,14 +226,11 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
- self.stateless_interactivity().key_listeners.push((
- TypeId::of::<KeyUpEvent>(),
- Box::new(move |view, event, _, phase, cx| {
- let event = event.downcast_ref().unwrap();
- listener(view, event, phase, cx);
- None
- }),
- ));
+ self.stateless_interactivity()
+ .key_up_listeners
+ .push(Box::new(move |view, event, phase, cx| {
+ listener(view, event, phase, cx)
+ }));
self
}
@@ -396,43 +376,6 @@ pub trait ElementInteractivity<V: 'static>: 'static {
fn as_stateful(&self) -> Option<&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(
&self,
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(
&mut self,
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;
pub struct StatelessInteractivity<V> {
- pub dispatch_context: DispatchContext,
+ pub dispatch_context: KeyContext,
pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
- pub key_listeners: SmallVec<[(TypeId, KeyListener<V>); 32]>,
+ pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,
+ pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,
+ pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,
pub hover_style: StyleRefinement,
pub group_hover_style: Option<GroupStyle>,
drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
@@ -910,12 +875,14 @@ impl InteractiveElementState {
impl<V> Default for StatelessInteractivity<V> {
fn default() -> Self {
Self {
- dispatch_context: DispatchContext::default(),
+ dispatch_context: KeyContext::default(),
mouse_down_listeners: SmallVec::new(),
mouse_up_listeners: SmallVec::new(),
mouse_move_listeners: SmallVec::new(),
scroll_wheel_listeners: SmallVec::new(),
- key_listeners: SmallVec::new(),
+ key_down_listeners: SmallVec::new(),
+ key_up_listeners: SmallVec::new(),
+ action_listeners: SmallVec::new(),
hover_style: StyleRefinement::default(),
group_hover_style: None,
drag_over_styles: SmallVec::new(),
@@ -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 type KeyListener<V> = Box<
- dyn Fn(
- &mut V,
- &dyn Any,
- &[&DispatchContext],
- DispatchPhase,
- &mut ViewContext<V>,
- ) -> Option<Box<dyn Action>>
- + 'static,
->;
+pub(crate) type KeyDownListener<V> =
+ Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
+
+pub(crate) type KeyUpListener<V> =
+ Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
+
+pub type ActionListener<V> =
+ Box<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static>;
#[cfg(test)]
mod test {
@@ -1282,9 +1247,10 @@ mod test {
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
div().id("testview").child(
div()
+ .context("test")
+ .track_focus(&self.focus_handle)
.on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
- .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)
- .track_focus(&self.focus_handle),
+ .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true),
)
}
}
@@ -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 smallvec::SmallVec;
pub struct KeyBinding {
action: Box<dyn Action>,
pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
- pub(super) context_predicate: Option<DispatchContextPredicate>,
+ pub(super) context_predicate: Option<KeyBindingContextPredicate>,
}
impl KeyBinding {
@@ -15,7 +15,7 @@ impl KeyBinding {
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
let context = if let Some(context) = context {
- Some(DispatchContextPredicate::parse(context)?)
+ Some(KeyBindingContextPredicate::parse(context)?)
} else {
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
.as_ref()
.map(|predicate| predicate.eval(contexts))
@@ -42,7 +42,7 @@ impl KeyBinding {
pub fn match_keystrokes(
&self,
pending_keystrokes: &[Keystroke],
- contexts: &[&DispatchContext],
+ contexts: &[KeyContext],
) -> KeyMatch {
if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
&& self.matches_context(contexts)
@@ -61,7 +61,7 @@ impl KeyBinding {
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
- contexts: &[&DispatchContext],
+ contexts: &[KeyContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
if self.action.partial_eq(action) && self.matches_context(contexts) {
Some(self.keystrokes.clone())
@@ -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 smallvec::SmallVec;
use std::{any::TypeId, collections::HashMap};
@@ -11,7 +11,7 @@ pub struct Keymap {
bindings: Vec<KeyBinding>,
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
disabled_keystrokes:
- HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<DispatchContextPredicate>>>,
+ HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeyBindingContextPredicate>>>,
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 smallvec::SmallVec;
use std::sync::Arc;
-pub struct KeyMatcher {
+pub struct KeystrokeMatcher {
pending_keystrokes: Vec<Keystroke>,
keymap: Arc<Mutex<Keymap>>,
keymap_version: KeymapVersion,
}
-impl KeyMatcher {
+impl KeystrokeMatcher {
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
let keymap_version = keymap.lock().version();
Self {
@@ -44,7 +44,7 @@ impl KeyMatcher {
pub fn match_keystroke(
&mut self,
keystroke: &Keystroke,
- context_stack: &[&DispatchContext],
+ context_stack: &[KeyContext],
) -> KeyMatch {
let keymap = self.keymap.lock();
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
@@ -86,7 +86,7 @@ impl KeyMatcher {
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
- contexts: &[&DispatchContext],
+ contexts: &[KeyContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
self.keymap
.lock()
@@ -1,7 +1,9 @@
mod binding;
+mod context;
mod keymap;
mod matcher;
pub use binding::*;
+pub use context::*;
pub use keymap::*;
pub use matcher::*;
@@ -0,0 +1 @@
+pub use crate::{Context, ParentElement, Refineable};
@@ -184,10 +184,6 @@ impl AnyView {
.compute_layout(layout_id, available_space);
(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 {
@@ -1,9 +1,9 @@
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,
- DevicePixels, DispatchContext, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
- FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent,
- IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers,
+ DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
+ EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
+ InputEvent, IsZero, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext, Modifiers,
MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
@@ -60,16 +60,7 @@ pub enum DispatchPhase {
}
type AnyObserver = Box<dyn FnMut(&mut WindowContext) -> bool + 'static>;
-type AnyListener = 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 AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + '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.
pub(crate) fn contains(&self, other: Self, cx: &WindowContext) -> bool {
- let mut ancestor = Some(other);
- while let Some(ancestor_id) = ancestor {
- if *self == ancestor_id {
- return true;
- } else {
- ancestor = cx
- .window
- .current_frame
- .focus_parents_by_child
- .get(&ancestor_id)
- .copied();
- }
- }
- false
+ cx.window
+ .current_frame
+ .dispatch_tree
+ .focus_contains(*self, other)
}
}
@@ -227,20 +208,31 @@ pub struct Window {
pub(crate) focus: Option<FocusId>,
}
-#[derive(Default)]
+// #[derive(Default)]
pub(crate) struct Frame {
element_states: HashMap<GlobalElementId, AnyBox>,
- key_matchers: HashMap<GlobalElementId, KeyMatcher>,
- mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
+ mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
+ pub(crate) dispatch_tree: DispatchTree,
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,
z_index_stack: StackingOrder,
content_mask_stack: Vec<ContentMask<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 {
@@ -309,8 +301,8 @@ impl Window {
layout_engine: TaffyLayoutEngine::new(),
root_view: None,
element_id_stack: GlobalElementId::default(),
- previous_frame: Frame::default(),
- current_frame: Frame::default(),
+ previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone())),
+ current_frame: Frame::new(DispatchTree::new(cx.keymap.clone())),
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
focus_listeners: SubscriberSet::new(),
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
/// 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.
@@ -407,21 +387,16 @@ impl<'a> WindowContext<'a> {
/// Move focus to the element associated with the given `FocusHandle`.
pub fn focus(&mut self, handle: &FocusHandle) {
- if self.window.focus == Some(handle.id) {
- return;
- }
+ let focus_id = handle.id;
if self.window.last_blur.is_none() {
self.window.last_blur = Some(self.window.focus);
}
- self.window.focus = Some(handle.id);
-
- // self.window.current_frame.key_dispatch_stack.clear()
- // self.window.root_view.initialize()
+ self.window.focus = Some(focus_id);
self.app.push_effect(Effect::FocusChanged {
window_handle: self.window.handle,
- focused: Some(handle.id),
+ focused: Some(focus_id),
});
self.notify();
}
@@ -441,11 +416,18 @@ impl<'a> WindowContext<'a> {
}
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
- self.defer(|cx| {
- cx.app.propagate_event = true;
- let stack = cx.dispatch_stack();
- cx.dispatch_action_internal(action, &stack[..])
- })
+ if let Some(focus_handle) = self.focused() {
+ self.defer(move |cx| {
+ if let Some(node_id) = cx
+ .window
+ .current_frame
+ .dispatch_tree
+ .focusable_node_id(focus_handle.id)
+ {
+ cx.dispatch_action_on_node(node_id, action);
+ }
+ })
+ }
}
/// 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.
pub fn mouse_position(&self) -> Point<Pixels> {
self.window.mouse_position
@@ -1079,26 +1098,6 @@ impl<'a> WindowContext<'a> {
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.
/// We repopulate all state in the current frame during each paint.
fn start_frame(&mut self) {
@@ -1110,12 +1109,9 @@ impl<'a> WindowContext<'a> {
mem::swap(&mut window.previous_frame, &mut window.current_frame);
let frame = &mut window.current_frame;
frame.element_states.clear();
- frame.key_matchers.clear();
frame.mouse_listeners.values_mut().for_each(Vec::clear);
frame.focus_listeners.clear();
- frame.key_dispatch_stack.clear();
- frame.focus_parents_by_child.clear();
- frame.freeze_key_dispatch_stack = false;
+ frame.dispatch_tree.clear();
}
/// 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(mut handlers) = self
- .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));
+ self.dispatch_mouse_event(any_mouse_event);
+ } else if let Some(any_key_event) = event.keyboard_event() {
+ self.dispatch_key_event(any_key_event);
+ }
- // 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;
- }
+ !self.app.propagate_event
+ }
+
+ fn dispatch_mouse_event(&mut self, event: &dyn Any) {
+ if let Some(mut handlers) = self
+ .window
+ .current_frame
+ .mouse_listeners
+ .remove(&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(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;
- }
+ // 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
- && any_mouse_event.downcast_ref::<MouseUpEvent>().is_some()
- {
- self.active_drag = None;
- }
+ 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(&any_mouse_event.type_id())
- .into_iter()
- .flat_map(|handlers| handlers.drain(..)),
- );
+ // Just in case any handlers added new handlers, which is weird, but possible.
+ handlers.extend(
self.window
.current_frame
.mouse_listeners
- .insert(any_mouse_event.type_id(), handlers);
- }
- } else if let Some(any_key_event) = event.keyboard_event() {
- let key_dispatch_stack = mem::take(&mut self.window.current_frame.key_dispatch_stack);
- 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);
+ .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;
}
}
}
- 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;
- }
+ // 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;
}
}
- KeyDispatchStackFrame::Context(_) => {
- context_stack.pop();
- }
}
+
+ context_stack.pop();
}
}
-
- drop(context_stack);
- self.window.current_frame.key_dispatch_stack = key_dispatch_stack;
}
-
- !self.app.propagate_event
}
- /// Attempt to map a keystroke to an action based on the keymap.
- pub fn match_keystroke(
- &mut self,
- element_id: &GlobalElementId,
- keystroke: &Keystroke,
- context_stack: &[&DispatchContext],
- ) -> KeyMatch {
- let key_match = self
+ fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box<dyn Action>) {
+ let dispatch_path = self
.window
.current_frame
- .key_matchers
- .get_mut(element_id)
- .unwrap()
- .match_keystroke(keystroke, context_stack);
-
- if key_match.is_some() {
- for matcher in self.window.current_frame.key_matchers.values_mut() {
- matcher.clear_pending();
+ .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
@@ -1345,105 +1367,14 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.prompt(level, msg, answers)
}
- pub fn available_actions(&self) -> impl Iterator<Item = Box<dyn Action>> + '_ {
- let key_dispatch_stack = &self.window.previous_frame.key_dispatch_stack;
- key_dispatch_stack.iter().filter_map(|frame| {
- match frame {
- // todo!factor out a KeyDispatchStackFrame::Action
- KeyDispatchStackFrame::Listener {
- event_type,
- listener: _,
- } => {
- 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);
- }
+ pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
+ if let Some(focus_id) = self.window.focus {
+ self.window
+ .current_frame
+ .dispatch_tree
+ .available_actions(focus_id)
+ } else {
+ Vec::new()
}
}
}
@@ -1609,22 +1540,9 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
id: impl Into<ElementId>,
f: impl FnOnce(GlobalElementId, &mut Self) -> R,
) -> R {
- let keymap = self.app_mut().keymap.clone();
let window = self.window_mut();
window.element_id_stack.push(id.into());
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 window: &mut Window = self.borrow_mut();
window.element_id_stack.pop();
@@ -2109,94 +2027,28 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}));
}
- pub fn with_key_listeners<R>(
- &mut self,
- key_listeners: impl IntoIterator<Item = (TypeId, KeyListener<V>)>,
- f: impl FnOnce(&mut Self) -> R,
- ) -> R {
- let old_stack_len = self.window.current_frame.key_dispatch_stack.len();
- 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);
-
- if !self.window.current_frame.freeze_key_dispatch_stack {
- self.window
- .current_frame
- .key_dispatch_stack
- .truncate(old_stack_len);
- }
-
- result
- }
-
- pub fn with_key_dispatch_context<R>(
+ pub fn with_key_dispatch<R>(
&mut self,
- context: DispatchContext,
- f: impl FnOnce(&mut Self) -> R,
+ context: KeyContext,
+ focus_handle: Option<FocusHandle>,
+ f: impl FnOnce(Option<FocusHandle>, &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);
+ let window = &mut self.window;
- 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
+ window
+ .current_frame
+ .dispatch_tree
+ .push_node(context, &mut window.previous_frame.dispatch_tree);
+ if let Some(focus_handle) = focus_handle.as_ref() {
+ window
.current_frame
- .focus_parents_by_child
- .insert(focus_handle.id, parent_focus_id);
+ .dispatch_tree
+ .make_focusable(focus_handle.id);
}
- self.window.current_frame.focus_stack.push(focus_handle.id);
+ let result = f(focus_handle, self);
- 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.dispatch_tree.pop_node();
- self.window.current_frame.focus_stack.pop();
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
/// platform to receive textual input with proper integration with concerns such
/// as IME interactions.
@@ -1,8 +1,7 @@
use editor::Editor;
use gpui::{
- div, uniform_list, Component, Div, FocusEnabled, ParentElement, Render, StatefulInteractivity,
- StatelessInteractive, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
- WindowContext,
+ div, uniform_list, Component, Div, ParentElement, Render, StatelessInteractive, Styled, Task,
+ UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext,
};
use std::{cmp, sync::Arc};
use ui::{prelude::*, v_stack, Divider, Label, LabelColor};
@@ -140,13 +139,11 @@ impl<D: PickerDelegate> 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 {
div()
.context("picker")
- .id("picker-container")
- .focusable()
.size_full()
.elevation_2(cx)
.on_action(Self::select_next)
@@ -1,12 +1,16 @@
use gpui::{
- actions, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render,
- StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext, WindowContext,
+ actions, div, Div, FocusHandle, Focusable, FocusableKeyDispatch, KeyBinding, ParentElement,
+ Render, StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext,
+ WindowContext,
};
use theme2::ActiveTheme;
actions!(ActionA, ActionB, ActionC);
-pub struct FocusStory {}
+pub struct FocusStory {
+ child_1_focus: FocusHandle,
+ child_2_focus: FocusHandle,
+}
impl FocusStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
@@ -16,12 +20,15 @@ impl FocusStory {
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 {
- 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 {
let theme = cx.theme();
@@ -31,18 +38,16 @@ impl Render for FocusStory {
let color_4 = theme.status().conflict;
let color_5 = theme.status().ignored;
let color_6 = theme.status().renamed;
- let child_1 = cx.focus_handle();
- let child_2 = cx.focus_handle();
div()
.id("parent")
.focusable()
.context("parent")
.on_action(|_, action: &ActionA, cx| {
- println!("Action A dispatched on parent during");
+ println!("Action A dispatched on parent");
})
.on_action(|_, action: &ActionB, cx| {
- println!("Action B dispatched on parent during");
+ println!("Action B dispatched on parent");
})
.on_focus(|_, _, _| println!("Parent focused"))
.on_blur(|_, _, _| println!("Parent blurred"))
@@ -56,7 +61,7 @@ impl Render for FocusStory {
.focus_in(|style| style.bg(color_3))
.child(
div()
- .track_focus(&child_1)
+ .track_focus(&self.child_1_focus)
.context("child-1")
.on_action(|_, action: &ActionB, cx| {
println!("Action B dispatched on child 1 during");
@@ -76,10 +81,10 @@ impl Render for FocusStory {
)
.child(
div()
- .track_focus(&child_2)
+ .track_focus(&self.child_2_focus)
.context("child-2")
.on_action(|_, action: &ActionC, cx| {
- println!("Action C dispatched on child 2 during");
+ println!("Action C dispatched on child 2");
})
.w_full()
.h_6()
@@ -1,4 +1,4 @@
-use gpui::{Div, ElementFocus, ElementInteractivity, Styled, UniformList, ViewContext};
+use gpui::{Div, ElementInteractivity, KeyDispatch, Styled, UniformList, ViewContext};
use theme2::ActiveTheme;
use crate::{ElevationIndex, UITextSize};
@@ -96,7 +96,7 @@ pub trait StyledExt: Styled + Sized {
impl<V, I, F> StyledExt for Div<V, I, F>
where
I: ElementInteractivity<V>,
- F: ElementFocus<V>,
+ F: KeyDispatch<V>,
{
}
@@ -37,10 +37,10 @@ use futures::{
};
use gpui::{
actions, div, point, rems, size, Action, AnyModel, AnyView, AnyWeakView, AppContext,
- AsyncAppContext, AsyncWindowContext, Bounds, Component, DispatchContext, Div, Entity, EntityId,
- EventEmitter, FocusHandle, GlobalPixels, Model, ModelContext, ParentElement, Point, Render,
- Size, StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled, Subscription,
- Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
+ AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter,
+ FocusHandle, GlobalPixels, KeyContext, Model, ModelContext, ParentElement, Point, Render, Size,
+ StatefulInteractive, StatelessInteractive, StatelessInteractivity, Styled, Subscription, Task,
+ View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
WindowOptions,
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
@@ -534,8 +534,8 @@ pub struct Workspace {
workspace_actions: Vec<
Box<
dyn Fn(
- Div<Workspace, StatefulInteractivity<Workspace>>,
- ) -> Div<Workspace, StatefulInteractivity<Workspace>>,
+ Div<Workspace, StatelessInteractivity<Workspace>>,
+ ) -> Div<Workspace, StatelessInteractivity<Workspace>>,
>,
>,
zoomed: Option<AnyWeakView>,
@@ -3514,8 +3514,8 @@ impl Workspace {
fn add_workspace_actions_listeners(
&self,
- mut div: Div<Workspace, StatefulInteractivity<Workspace>>,
- ) -> Div<Workspace, StatefulInteractivity<Workspace>> {
+ mut div: Div<Workspace, StatelessInteractivity<Workspace>>,
+ ) -> Div<Workspace, StatelessInteractivity<Workspace>> {
for action in self.workspace_actions.iter() {
div = (action)(div)
}
@@ -3743,158 +3743,159 @@ impl Render for Workspace {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
- let mut context = DispatchContext::default();
- context.insert("Workspace");
- cx.with_key_dispatch_context(context, |cx| {
- div()
- .relative()
- .size_full()
- .flex()
- .flex_col()
- .font("Zed Sans")
- .gap_0()
- .justify_start()
- .items_start()
- .text_color(cx.theme().colors().text)
- .bg(cx.theme().colors().background)
- .child(self.render_titlebar(cx))
- .child(
- // todo! should this be a component a view?
- self.add_workspace_actions_listeners(div().id("workspace"))
- .relative()
- .flex_1()
- .w_full()
- .flex()
- .overflow_hidden()
- .border_t()
- .border_b()
- .border_color(cx.theme().colors().border)
- .child(self.modal_layer.clone())
- // .children(
- // Some(
- // Panel::new("project-panel-outer", cx)
- // .side(PanelSide::Left)
- // .child(ProjectPanel::new("project-panel-inner")),
- // )
- // .filter(|_| self.is_project_panel_open()),
- // )
- // .children(
- // Some(
- // Panel::new("collab-panel-outer", cx)
- // .child(CollabPanel::new("collab-panel-inner"))
- // .side(PanelSide::Left),
- // )
- // .filter(|_| self.is_collab_panel_open()),
- // )
- // .child(NotificationToast::new(
- // "maxbrunsfeld has requested to add you as a contact.".into(),
- // ))
- .child(
- div().flex().flex_col().flex_1().h_full().child(
- div().flex().flex_1().child(self.center.render(
- &self.project,
- &self.follower_states,
- self.active_call(),
- &self.active_pane,
- self.zoomed.as_ref(),
- &self.app_state,
- cx,
- )),
- ), // .children(
- // Some(
- // Panel::new("terminal-panel", cx)
- // .child(Terminal::new())
- // .allowed_sides(PanelAllowedSides::BottomOnly)
- // .side(PanelSide::Bottom),
- // )
- // .filter(|_| self.is_terminal_open()),
- // ),
+ let mut context = KeyContext::default();
+ context.add("Workspace");
+
+ self.add_workspace_actions_listeners(div())
+ .context(context)
+ .relative()
+ .size_full()
+ .flex()
+ .flex_col()
+ .font("Zed Sans")
+ .gap_0()
+ .justify_start()
+ .items_start()
+ .text_color(cx.theme().colors().text)
+ .bg(cx.theme().colors().background)
+ .child(self.render_titlebar(cx))
+ .child(
+ // todo! should this be a component a view?
+ div()
+ .id("workspace")
+ .relative()
+ .flex_1()
+ .w_full()
+ .flex()
+ .overflow_hidden()
+ .border_t()
+ .border_b()
+ .border_color(cx.theme().colors().border)
+ .child(self.modal_layer.clone())
+ // .children(
+ // Some(
+ // Panel::new("project-panel-outer", cx)
+ // .side(PanelSide::Left)
+ // .child(ProjectPanel::new("project-panel-inner")),
+ // )
+ // .filter(|_| self.is_project_panel_open()),
+ // )
+ // .children(
+ // Some(
+ // Panel::new("collab-panel-outer", cx)
+ // .child(CollabPanel::new("collab-panel-inner"))
+ // .side(PanelSide::Left),
+ // )
+ // .filter(|_| self.is_collab_panel_open()),
+ // )
+ // .child(NotificationToast::new(
+ // "maxbrunsfeld has requested to add you as a contact.".into(),
+ // ))
+ .child(
+ div().flex().flex_col().flex_1().h_full().child(
+ div().flex().flex_1().child(self.center.render(
+ &self.project,
+ &self.follower_states,
+ self.active_call(),
+ &self.active_pane,
+ self.zoomed.as_ref(),
+ &self.app_state,
+ cx,
+ )),
), // .children(
// Some(
- // Panel::new("chat-panel-outer", cx)
- // .side(PanelSide::Right)
- // .child(ChatPanel::new("chat-panel-inner").messages(vec![
- // ChatMessage::new(
- // "osiewicz".to_string(),
- // "is this thing on?".to_string(),
- // DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
- // .unwrap()
- // .naive_local(),
- // ),
- // ChatMessage::new(
- // "maxdeviant".to_string(),
- // "Reading you loud and clear!".to_string(),
- // DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
- // .unwrap()
- // .naive_local(),
- // ),
- // ])),
+ // Panel::new("terminal-panel", cx)
+ // .child(Terminal::new())
+ // .allowed_sides(PanelAllowedSides::BottomOnly)
+ // .side(PanelSide::Bottom),
// )
- // .filter(|_| self.is_chat_panel_open()),
- // )
- // .children(
- // Some(
- // Panel::new("notifications-panel-outer", cx)
- // .side(PanelSide::Right)
- // .child(NotificationsPanel::new("notifications-panel-inner")),
- // )
- // .filter(|_| self.is_notifications_panel_open()),
- // )
- // .children(
- // Some(
- // Panel::new("assistant-panel-outer", cx)
- // .child(AssistantPanel::new("assistant-panel-inner")),
- // )
- // .filter(|_| self.is_assistant_panel_open()),
+ // .filter(|_| self.is_terminal_open()),
// ),
- )
- .child(self.status_bar.clone())
- // .when(self.debug.show_toast, |this| {
- // this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
- // })
- // .children(
- // Some(
- // div()
- // .absolute()
- // .top(px(50.))
- // .left(px(640.))
- // .z_index(8)
- // .child(LanguageSelector::new("language-selector")),
- // )
- // .filter(|_| self.is_language_selector_open()),
- // )
- .z_index(8)
- // Debug
- .child(
- div()
- .flex()
- .flex_col()
- .z_index(9)
- .absolute()
- .top_20()
- .left_1_4()
- .w_40()
- .gap_2(), // .when(self.show_debug, |this| {
- // this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
- // Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
- // ))
- // .child(
- // Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
- // |workspace, cx| workspace.debug_toggle_toast(cx),
- // )),
- // )
- // .child(
- // Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
- // |workspace, cx| workspace.debug_toggle_livestream(cx),
- // )),
- // )
- // })
- // .child(
- // Button::<Workspace>::new("Toggle Debug")
- // .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
- // ),
- )
- })
+ ), // .children(
+ // Some(
+ // Panel::new("chat-panel-outer", cx)
+ // .side(PanelSide::Right)
+ // .child(ChatPanel::new("chat-panel-inner").messages(vec![
+ // ChatMessage::new(
+ // "osiewicz".to_string(),
+ // "is this thing on?".to_string(),
+ // DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
+ // .unwrap()
+ // .naive_local(),
+ // ),
+ // ChatMessage::new(
+ // "maxdeviant".to_string(),
+ // "Reading you loud and clear!".to_string(),
+ // DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
+ // .unwrap()
+ // .naive_local(),
+ // ),
+ // ])),
+ // )
+ // .filter(|_| self.is_chat_panel_open()),
+ // )
+ // .children(
+ // Some(
+ // Panel::new("notifications-panel-outer", cx)
+ // .side(PanelSide::Right)
+ // .child(NotificationsPanel::new("notifications-panel-inner")),
+ // )
+ // .filter(|_| self.is_notifications_panel_open()),
+ // )
+ // .children(
+ // Some(
+ // Panel::new("assistant-panel-outer", cx)
+ // .child(AssistantPanel::new("assistant-panel-inner")),
+ // )
+ // .filter(|_| self.is_assistant_panel_open()),
+ // ),
+ )
+ .child(self.status_bar.clone())
+ // .when(self.debug.show_toast, |this| {
+ // this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
+ // })
+ // .children(
+ // Some(
+ // div()
+ // .absolute()
+ // .top(px(50.))
+ // .left(px(640.))
+ // .z_index(8)
+ // .child(LanguageSelector::new("language-selector")),
+ // )
+ // .filter(|_| self.is_language_selector_open()),
+ // )
+ .z_index(8)
+ // Debug
+ .child(
+ div()
+ .flex()
+ .flex_col()
+ .z_index(9)
+ .absolute()
+ .top_20()
+ .left_1_4()
+ .w_40()
+ .gap_2(), // .when(self.show_debug, |this| {
+ // this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
+ // Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
+ // ))
+ // .child(
+ // Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
+ // |workspace, cx| workspace.debug_toggle_toast(cx),
+ // )),
+ // )
+ // .child(
+ // Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
+ // |workspace, cx| workspace.debug_toggle_livestream(cx),
+ // )),
+ // )
+ // })
+ // .child(
+ // Button::<Workspace>::new("Toggle Debug")
+ // .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
+ // ),
+ )
}
}
// todo!()