From 74a0d9316a850f7e39c9a6a9603831129600d594 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Nov 2023 11:56:14 -0700 Subject: [PATCH 01/15] Add a DispatchTree which will replace the existing key dispatch strategy Instead of freezing a stack, we will record the entire dispatch tree so we can change focus. Co-Authored-By: Antonio Scandurra --- crates/editor2/src/editor.rs | 22 +- crates/editor2/src/element.rs | 28 +- crates/gpui/src/dispatch.rs | 1 + crates/gpui2/src/action.rs | 400 +------------------------ crates/gpui2/src/dispatch.rs | 225 ++++++++++++++ crates/gpui2/src/gpui2.rs | 1 + crates/gpui2/src/interactive.rs | 28 +- crates/gpui2/src/keymap/binding.rs | 12 +- crates/gpui2/src/keymap/context.rs | 434 ++++++++++++++++++++++++++++ crates/gpui2/src/keymap/keymap.rs | 4 +- crates/gpui2/src/keymap/matcher.rs | 10 +- crates/gpui2/src/keymap/mod.rs | 2 + crates/gpui2/src/window.rs | 34 +-- crates/workspace2/src/workspace2.rs | 14 +- 14 files changed, 724 insertions(+), 491 deletions(-) create mode 100644 crates/gpui/src/dispatch.rs create mode 100644 crates/gpui2/src/dispatch.rs create mode 100644 crates/gpui2/src/keymap/context.rs diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 5bc67a57b6..ad17f96055 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -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, KeyBindingContext, 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, workspace: Option<(WeakView, i64)>, - keymap_context_layers: BTreeMap, + keymap_context_layers: BTreeMap, input_enabled: bool, read_only: bool, leader_peer_id: Option, @@ -1980,9 +1980,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) -> KeyBindingContext { + let mut dispatch_context = KeyBindingContext::default(); + dispatch_context.add("Editor"); let mode = match self.mode { EditorMode::SingleLine => "single_line", EditorMode::AutoHeight { .. } => "auto_height", @@ -1990,17 +1990,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 => {} } diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 67fcbaa4ba..2cd319f66b 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -16,11 +16,12 @@ 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, + KeyBindingContext, KeyDownEvent, KeyListener, 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; @@ -4157,21 +4158,6 @@ fn build_key_listeners( 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 - }, - ), ] } @@ -4179,7 +4165,7 @@ fn build_key_listener( listener: impl Fn( &mut Editor, &T, - &[&DispatchContext], + &[&KeyBindingContext], DispatchPhase, &mut ViewContext, ) -> Option> diff --git a/crates/gpui/src/dispatch.rs b/crates/gpui/src/dispatch.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/crates/gpui/src/dispatch.rs @@ -0,0 +1 @@ + diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index 170ddf942f..6526f96cb9 100644 --- a/crates/gpui2/src/action.rs +++ b/crates/gpui2/src/action.rs @@ -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, - map: HashMap, -} - -impl<'a> TryFrom<&'a str> for DispatchContext { - type Error = anyhow::Error; - - fn try_from(value: &'a str) -> Result { - Self::parse(value) - } -} - -impl DispatchContext { - pub fn parse(source: &str) -> Result { - 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::(); - 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::(); - 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>(&mut self, identifier: I) { - self.set.insert(identifier.into()); - } - - pub fn set, S2: Into>(&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, Box), - Not(Box), - And(Box, Box), - Or(Box, Box), -} - -impl DispatchContextPredicate { - pub fn parse(source: &str) -> Result { - 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; - - 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 { - Ok(Self::Or(Box::new(self), Box::new(other))) - } - - fn new_and(self, other: Self) -> Result { - Ok(Self::And(Box::new(self), Box::new(other))) - } - - fn new_child(self, other: Self) -> Result { - Ok(Self::Child(Box::new(self), Box::new(other))) - } - - fn new_eq(self, other: Self) -> Result { - 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 { - 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())), - ) - ); - } -} diff --git a/crates/gpui2/src/dispatch.rs b/crates/gpui2/src/dispatch.rs new file mode 100644 index 0000000000..372c8c2610 --- /dev/null +++ b/crates/gpui2/src/dispatch.rs @@ -0,0 +1,225 @@ +use crate::{ + Action, DispatchPhase, FocusId, KeyBindingContext, KeyDownEvent, KeyMatch, Keymap, + KeystrokeMatcher, WindowContext, +}; +use collections::HashMap; +use parking_lot::Mutex; +use smallvec::SmallVec; +use std::{any::Any, sync::Arc}; + +// trait KeyListener -> FnMut(&E, &mut V, &mut ViewContext) +type AnyKeyListener = Box; +type AnyActionListener = Box; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub struct DispatchNodeId(usize); + +pub struct DispatchTree { + node_stack: Vec, + context_stack: Vec, + nodes: Vec, + focused: Option, + focusable_node_ids: HashMap, + keystroke_matchers: HashMap, KeystrokeMatcher>, + keymap: Arc>, +} + +#[derive(Default)] +pub struct DispatchNode { + key_listeners: SmallVec<[AnyKeyListener; 2]>, + action_listeners: SmallVec<[AnyActionListener; 16]>, + context: KeyBindingContext, + parent: Option, +} + +impl DispatchTree { + pub fn clear(&mut self) { + self.node_stack.clear(); + self.nodes.clear(); + } + + pub fn push_node(&mut self, context: Option, old_tree: &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 let Some(context) = context { + self.context_stack.push(context); + if let Some((context_stack, matcher)) = old_tree + .keystroke_matchers + .remove_entry(self.context_stack.as_slice()) + { + self.keystroke_matchers.insert(context_stack, matcher); + } + } + } + + pub fn pop_node(&mut self) -> DispatchNodeId { + self.node_stack.pop().unwrap() + } + + pub fn on_key_event(&mut self, listener: AnyKeyListener) { + self.active_node().key_listeners.push(listener); + } + + pub fn on_action(&mut self, listener: AnyActionListener) { + self.active_node().action_listeners.push(listener); + } + + pub fn make_focusable(&mut self, focus_id: FocusId) { + self.focusable_node_ids + .insert(focus_id, self.active_node_id()); + } + + pub fn set_focus(&mut self, focus_id: Option) { + self.focused = focus_id; + } + + pub fn active_node(&mut self) -> &mut DispatchNode { + let node_id = self.active_node_id(); + &mut self.nodes[node_id.0] + } + + fn active_node_id(&self) -> DispatchNodeId { + *self.node_stack.last().unwrap() + } + + /// Returns the DispatchNodeIds from the root of the tree to the given target node id. + 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 dispatch_key(&mut self, event: &dyn Any, cx: &mut WindowContext) { + if let Some(focused_node_id) = self + .focused + .and_then(|focus_id| self.focusable_node_ids.get(&focus_id)) + .copied() + { + self.dispatch_key_on_node(focused_node_id, event, cx); + } + } + + fn dispatch_key_on_node( + &mut self, + node_id: DispatchNodeId, + event: &dyn Any, + cx: &mut WindowContext, + ) { + let dispatch_path = self.dispatch_path(node_id); + + // Capture phase + self.context_stack.clear(); + cx.propagate_event = true; + for node_id in &dispatch_path { + let node = &self.nodes[node_id.0]; + if !node.context.is_empty() { + self.context_stack.push(node.context.clone()); + } + + for key_listener in &node.key_listeners { + key_listener(event, DispatchPhase::Capture, cx); + if !cx.propagate_event { + return; + } + } + } + + // Bubble phase + for node_id in dispatch_path.iter().rev() { + let node = &self.nodes[node_id.0]; + + // Handle low level key events + for key_listener in &node.key_listeners { + key_listener(event, DispatchPhase::Bubble, cx); + if !cx.propagate_event { + return; + } + } + + // Match keystrokes + if !node.context.is_empty() { + if let Some(key_down_event) = event.downcast_ref::() { + if !self + .keystroke_matchers + .contains_key(self.context_stack.as_slice()) + { + let keystroke_contexts = self.context_stack.iter().cloned().collect(); + self.keystroke_matchers.insert( + keystroke_contexts, + KeystrokeMatcher::new(self.keymap.clone()), + ); + } + + if let Some(keystroke_matcher) = self + .keystroke_matchers + .get_mut(self.context_stack.as_slice()) + { + if let KeyMatch::Some(action) = keystroke_matcher.match_keystroke( + &key_down_event.keystroke, + self.context_stack.as_slice(), + ) { + self.dispatch_action_on_node(*node_id, action, cx); + if !cx.propagate_event { + return; + } + } + } + } + + self.context_stack.pop(); + } + } + } + + pub fn dispatch_action(&self, action: Box, cx: &mut WindowContext) { + if let Some(focused_node_id) = self + .focused + .and_then(|focus_id| self.focusable_node_ids.get(&focus_id)) + .copied() + { + self.dispatch_action_on_node(focused_node_id, action, cx); + } + } + + fn dispatch_action_on_node( + &self, + node_id: DispatchNodeId, + action: Box, + cx: &mut WindowContext, + ) { + let dispatch_path = self.dispatch_path(node_id); + + // Capture phase + for node_id in &dispatch_path { + let node = &self.nodes[node_id.0]; + for action_listener in &node.action_listeners { + action_listener(&action, DispatchPhase::Capture, cx); + if !cx.propagate_event { + return; + } + } + } + + // Bubble phase + for node_id in dispatch_path.iter().rev() { + let node = &self.nodes[node_id.0]; + for action_listener in &node.action_listeners { + cx.propagate_event = false; // Actions stop propagation by default during the bubble phase + action_listener(&action, DispatchPhase::Capture, cx); + if !cx.propagate_event { + return; + } + } + } + } +} diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 79275005d2..42aea446f1 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -3,6 +3,7 @@ mod action; mod app; mod assets; mod color; +mod dispatch; mod element; mod elements; mod executor; diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 243eb3cb07..946a59a809 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,6 +1,6 @@ use crate::{ div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds, - Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch, + Component, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyBindingContext, KeyMatch, Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, Task, View, ViewContext, }; @@ -167,7 +167,7 @@ pub trait StatelessInteractive: Element { fn context(mut self, context: C) -> Self where Self: Sized, - C: TryInto, + C: TryInto, C::Error: Debug, { self.stateless_interactivity().dispatch_context = @@ -403,24 +403,6 @@ pub trait ElementInteractivity: 'static { ) -> 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::(), - Box::new(move |_, key_down, context, phase, cx| { - if phase == DispatchPhase::Bubble { - let key_down = key_down.downcast_ref::().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) }) @@ -808,7 +790,7 @@ impl ElementInteractivity for StatefulInteractivity { type DropListener = dyn Fn(&mut V, AnyView, &mut ViewContext) + 'static; pub struct StatelessInteractivity { - pub dispatch_context: DispatchContext, + pub dispatch_context: KeyBindingContext, pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>, pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>, pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>, @@ -910,7 +892,7 @@ impl InteractiveElementState { impl Default for StatelessInteractivity { fn default() -> Self { Self { - dispatch_context: DispatchContext::default(), + dispatch_context: KeyBindingContext::default(), mouse_down_listeners: SmallVec::new(), mouse_up_listeners: SmallVec::new(), mouse_move_listeners: SmallVec::new(), @@ -1254,7 +1236,7 @@ pub type KeyListener = Box< dyn Fn( &mut V, &dyn Any, - &[&DispatchContext], + &[&KeyBindingContext], DispatchPhase, &mut ViewContext, ) -> Option> diff --git a/crates/gpui2/src/keymap/binding.rs b/crates/gpui2/src/keymap/binding.rs index 829f7a3b2c..1cf62484b9 100644 --- a/crates/gpui2/src/keymap/binding.rs +++ b/crates/gpui2/src/keymap/binding.rs @@ -1,11 +1,11 @@ -use crate::{Action, DispatchContext, DispatchContextPredicate, KeyMatch, Keystroke}; +use crate::{Action, KeyBindingContext, KeyBindingContextPredicate, KeyMatch, Keystroke}; use anyhow::Result; use smallvec::SmallVec; pub struct KeyBinding { action: Box, pub(super) keystrokes: SmallVec<[Keystroke; 2]>, - pub(super) context_predicate: Option, + pub(super) context_predicate: Option, } impl KeyBinding { @@ -15,7 +15,7 @@ impl KeyBinding { pub fn load(keystrokes: &str, action: Box, context: Option<&str>) -> Result { 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: &[KeyBindingContext]) -> 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: &[KeyBindingContext], ) -> 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: &[KeyBindingContext], ) -> Option> { if self.action.partial_eq(action) && self.matches_context(contexts) { Some(self.keystrokes.clone()) diff --git a/crates/gpui2/src/keymap/context.rs b/crates/gpui2/src/keymap/context.rs new file mode 100644 index 0000000000..834bd4989a --- /dev/null +++ b/crates/gpui2/src/keymap/context.rs @@ -0,0 +1,434 @@ +use crate::SharedString; +use anyhow::{anyhow, Result}; +use smallvec::SmallVec; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] +pub struct KeyBindingContext(SmallVec<[ContextEntry; 8]>); + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +struct ContextEntry { + key: SharedString, + value: Option, +} + +impl<'a> TryFrom<&'a str> for KeyBindingContext { + type Error = anyhow::Error; + + fn try_from(value: &'a str) -> Result { + Self::parse(value) + } +} + +impl KeyBindingContext { + pub fn parse(source: &str) -> Result { + 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::(); + 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::(); + 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>(&mut self, identifier: I) { + let key = identifier.into(); + + if !self.contains(&key) { + self.0.push(ContextEntry { key, value: None }) + } + } + + pub fn set, S2: Into>(&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() + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum KeyBindingContextPredicate { + Identifier(SharedString), + Equal(SharedString, SharedString), + NotEqual(SharedString, SharedString), + Child( + Box, + Box, + ), + Not(Box), + And( + Box, + Box, + ), + Or( + Box, + Box, + ), +} + +impl KeyBindingContextPredicate { + pub fn parse(source: &str) -> Result { + 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: &[KeyBindingContext]) -> 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; + + 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 { + Ok(Self::Or(Box::new(self), Box::new(other))) + } + + fn new_and(self, other: Self) -> Result { + Ok(Self::And(Box::new(self), Box::new(other))) + } + + fn new_child(self, other: Self) -> Result { + Ok(Self::Child(Box::new(self), Box::new(other))) + } + + fn new_eq(self, other: Self) -> Result { + 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 { + 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 = KeyBindingContext::default(); + expected.set("foo", "bar"); + expected.add("baz"); + assert_eq!(KeyBindingContext::parse("baz foo=bar").unwrap(), expected); + assert_eq!(KeyBindingContext::parse("foo = bar baz").unwrap(), expected); + assert_eq!( + KeyBindingContext::parse(" baz foo = bar baz").unwrap(), + expected + ); + assert_eq!( + KeyBindingContext::parse(" foo = bar baz").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())), + ) + ); + } +} diff --git a/crates/gpui2/src/keymap/keymap.rs b/crates/gpui2/src/keymap/keymap.rs index eda493a460..989ee7a8d5 100644 --- a/crates/gpui2/src/keymap/keymap.rs +++ b/crates/gpui2/src/keymap/keymap.rs @@ -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, binding_indices_by_action_id: HashMap>, disabled_keystrokes: - HashMap, HashSet>>, + HashMap, HashSet>>, version: KeymapVersion, } diff --git a/crates/gpui2/src/keymap/matcher.rs b/crates/gpui2/src/keymap/matcher.rs index c2033a9595..c9b5d26ecb 100644 --- a/crates/gpui2/src/keymap/matcher.rs +++ b/crates/gpui2/src/keymap/matcher.rs @@ -1,15 +1,15 @@ -use crate::{Action, DispatchContext, Keymap, KeymapVersion, Keystroke}; +use crate::{Action, KeyBindingContext, Keymap, KeymapVersion, Keystroke}; use parking_lot::Mutex; use smallvec::SmallVec; use std::sync::Arc; -pub struct KeyMatcher { +pub struct KeystrokeMatcher { pending_keystrokes: Vec, keymap: Arc>, keymap_version: KeymapVersion, } -impl KeyMatcher { +impl KeystrokeMatcher { pub fn new(keymap: Arc>) -> 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: &[KeyBindingContext], ) -> 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: &[KeyBindingContext], ) -> Option> { self.keymap .lock() diff --git a/crates/gpui2/src/keymap/mod.rs b/crates/gpui2/src/keymap/mod.rs index 449b5427bf..09e222c095 100644 --- a/crates/gpui2/src/keymap/mod.rs +++ b/crates/gpui2/src/keymap/mod.rs @@ -1,7 +1,9 @@ mod binding; +mod context; mod keymap; mod matcher; pub use binding::*; +pub use context::*; pub use keymap::*; pub use matcher::*; diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index fd3890d644..cde7b31754 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1,15 +1,15 @@ use crate::{ build_action_from_type, 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, - MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, - PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, - PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, - SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, - TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, - WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + DevicePixels, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, + FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, + KeyBindingContext, KeyListener, KeyMatch, Keystroke, KeystrokeMatcher, LayoutId, Model, + ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, + PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, + RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, + Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, + VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -64,7 +64,7 @@ type AnyListener = Box Option> @@ -230,7 +230,7 @@ pub struct Window { #[derive(Default)] pub(crate) struct Frame { element_states: HashMap, - key_matchers: HashMap, + key_matchers: HashMap, mouse_listeners: HashMap>, pub(crate) focus_listeners: Vec, pub(crate) key_dispatch_stack: Vec, @@ -337,7 +337,7 @@ pub(crate) enum KeyDispatchStackFrame { event_type: TypeId, listener: AnyKeyListener, }, - Context(DispatchContext), + Context(KeyBindingContext), } /// Indicates which region of the window is visible. Content falling outside of this mask will not be @@ -1228,7 +1228,7 @@ impl<'a> WindowContext<'a> { } 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(); + let mut context_stack = SmallVec::<[&KeyBindingContext; 16]>::new(); for (ix, frame) in key_dispatch_stack.iter().enumerate() { match frame { @@ -1300,7 +1300,7 @@ impl<'a> WindowContext<'a> { &mut self, element_id: &GlobalElementId, keystroke: &Keystroke, - context_stack: &[&DispatchContext], + context_stack: &[KeyBindingContext], ) -> KeyMatch { let key_match = self .window @@ -1621,7 +1621,7 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { .previous_frame .key_matchers .remove(&global_id) - .unwrap_or_else(|| KeyMatcher::new(keymap)), + .unwrap_or_else(|| KeystrokeMatcher::new(keymap)), ); } @@ -2120,7 +2120,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { let handle = self.view().downgrade(); let listener = Box::new( move |event: &dyn Any, - context_stack: &[&DispatchContext], + context_stack: &[&KeyBindingContext], phase: DispatchPhase, cx: &mut WindowContext<'_>| { handle @@ -2154,7 +2154,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { pub fn with_key_dispatch_context( &mut self, - context: DispatchContext, + context: KeyBindingContext, f: impl FnOnce(&mut Self) -> R, ) -> R { if context.is_empty() { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 5c678df317..1522b4ec4e 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -37,11 +37,11 @@ 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, - WindowOptions, + AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter, + FocusHandle, GlobalPixels, KeyBindingContext, Model, ModelContext, ParentElement, Point, + Render, Size, StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled, + Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, + WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; @@ -3743,8 +3743,8 @@ impl Render for Workspace { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let mut context = DispatchContext::default(); - context.insert("Workspace"); + let mut context = KeyBindingContext::default(); + context.add("Workspace"); cx.with_key_dispatch_context(context, |cx| { div() .relative() From 7eaba8fabc8f7e3d28459b713f352fb75a2663e3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Nov 2023 14:47:45 -0700 Subject: [PATCH 02/15] WIP --- crates/editor2/src/editor.rs | 8 +- crates/editor2/src/element.rs | 19 +- crates/gpui2/src/dispatch.rs | 225 ---------- crates/gpui2/src/elements/div.rs | 95 +++-- crates/gpui2/src/elements/img.rs | 26 +- crates/gpui2/src/elements/svg.rs | 28 +- crates/gpui2/src/focusable.rs | 252 ------------ crates/gpui2/src/gpui2.rs | 6 +- crates/gpui2/src/interactive.rs | 44 +- crates/gpui2/src/key_dispatch.rs | 547 +++++++++++++++++++++++++ crates/gpui2/src/keymap/binding.rs | 8 +- crates/gpui2/src/keymap/context.rs | 21 +- crates/gpui2/src/keymap/matcher.rs | 6 +- crates/gpui2/src/prelude.rs | 1 + crates/gpui2/src/view.rs | 4 - crates/gpui2/src/window.rs | 451 ++++---------------- crates/picker2/src/picker2.rs | 8 +- crates/storybook2/src/stories/focus.rs | 4 +- crates/ui2/src/styled_ext.rs | 4 +- crates/workspace2/src/workspace2.rs | 302 +++++++------- 20 files changed, 908 insertions(+), 1151 deletions(-) delete mode 100644 crates/gpui2/src/dispatch.rs delete mode 100644 crates/gpui2/src/focusable.rs create mode 100644 crates/gpui2/src/key_dispatch.rs create mode 100644 crates/gpui2/src/prelude.rs diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index ad17f96055..d432ae037c 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -42,7 +42,7 @@ use gpui::{ action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context, EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla, - InputHandler, KeyBindingContext, Model, MouseButton, ParentElement, Pixels, Render, + 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, workspace: Option<(WeakView, i64)>, - keymap_context_layers: BTreeMap, + keymap_context_layers: BTreeMap, input_enabled: bool, read_only: bool, leader_peer_id: Option, @@ -1980,8 +1980,8 @@ impl Editor { this } - fn dispatch_context(&self, cx: &AppContext) -> KeyBindingContext { - let mut dispatch_context = KeyBindingContext::default(); + 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", diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 2cd319f66b..d42a14bb77 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -18,10 +18,9 @@ use gpui::{ black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler, - KeyBindingContext, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, - MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, - ShapedGlyph, Size, Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, - WrappedLineLayout, + KeyContext, KeyDownEvent, KeyListener, 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; @@ -2457,11 +2456,11 @@ impl Element 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()), + |_, _| {}, + ) }); } @@ -4165,7 +4164,7 @@ fn build_key_listener( listener: impl Fn( &mut Editor, &T, - &[&KeyBindingContext], + &[&KeyContext], DispatchPhase, &mut ViewContext, ) -> Option> diff --git a/crates/gpui2/src/dispatch.rs b/crates/gpui2/src/dispatch.rs deleted file mode 100644 index 372c8c2610..0000000000 --- a/crates/gpui2/src/dispatch.rs +++ /dev/null @@ -1,225 +0,0 @@ -use crate::{ - Action, DispatchPhase, FocusId, KeyBindingContext, KeyDownEvent, KeyMatch, Keymap, - KeystrokeMatcher, WindowContext, -}; -use collections::HashMap; -use parking_lot::Mutex; -use smallvec::SmallVec; -use std::{any::Any, sync::Arc}; - -// trait KeyListener -> FnMut(&E, &mut V, &mut ViewContext) -type AnyKeyListener = Box; -type AnyActionListener = Box; - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -pub struct DispatchNodeId(usize); - -pub struct DispatchTree { - node_stack: Vec, - context_stack: Vec, - nodes: Vec, - focused: Option, - focusable_node_ids: HashMap, - keystroke_matchers: HashMap, KeystrokeMatcher>, - keymap: Arc>, -} - -#[derive(Default)] -pub struct DispatchNode { - key_listeners: SmallVec<[AnyKeyListener; 2]>, - action_listeners: SmallVec<[AnyActionListener; 16]>, - context: KeyBindingContext, - parent: Option, -} - -impl DispatchTree { - pub fn clear(&mut self) { - self.node_stack.clear(); - self.nodes.clear(); - } - - pub fn push_node(&mut self, context: Option, old_tree: &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 let Some(context) = context { - self.context_stack.push(context); - if let Some((context_stack, matcher)) = old_tree - .keystroke_matchers - .remove_entry(self.context_stack.as_slice()) - { - self.keystroke_matchers.insert(context_stack, matcher); - } - } - } - - pub fn pop_node(&mut self) -> DispatchNodeId { - self.node_stack.pop().unwrap() - } - - pub fn on_key_event(&mut self, listener: AnyKeyListener) { - self.active_node().key_listeners.push(listener); - } - - pub fn on_action(&mut self, listener: AnyActionListener) { - self.active_node().action_listeners.push(listener); - } - - pub fn make_focusable(&mut self, focus_id: FocusId) { - self.focusable_node_ids - .insert(focus_id, self.active_node_id()); - } - - pub fn set_focus(&mut self, focus_id: Option) { - self.focused = focus_id; - } - - pub fn active_node(&mut self) -> &mut DispatchNode { - let node_id = self.active_node_id(); - &mut self.nodes[node_id.0] - } - - fn active_node_id(&self) -> DispatchNodeId { - *self.node_stack.last().unwrap() - } - - /// Returns the DispatchNodeIds from the root of the tree to the given target node id. - 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 dispatch_key(&mut self, event: &dyn Any, cx: &mut WindowContext) { - if let Some(focused_node_id) = self - .focused - .and_then(|focus_id| self.focusable_node_ids.get(&focus_id)) - .copied() - { - self.dispatch_key_on_node(focused_node_id, event, cx); - } - } - - fn dispatch_key_on_node( - &mut self, - node_id: DispatchNodeId, - event: &dyn Any, - cx: &mut WindowContext, - ) { - let dispatch_path = self.dispatch_path(node_id); - - // Capture phase - self.context_stack.clear(); - cx.propagate_event = true; - for node_id in &dispatch_path { - let node = &self.nodes[node_id.0]; - if !node.context.is_empty() { - self.context_stack.push(node.context.clone()); - } - - for key_listener in &node.key_listeners { - key_listener(event, DispatchPhase::Capture, cx); - if !cx.propagate_event { - return; - } - } - } - - // Bubble phase - for node_id in dispatch_path.iter().rev() { - let node = &self.nodes[node_id.0]; - - // Handle low level key events - for key_listener in &node.key_listeners { - key_listener(event, DispatchPhase::Bubble, cx); - if !cx.propagate_event { - return; - } - } - - // Match keystrokes - if !node.context.is_empty() { - if let Some(key_down_event) = event.downcast_ref::() { - if !self - .keystroke_matchers - .contains_key(self.context_stack.as_slice()) - { - let keystroke_contexts = self.context_stack.iter().cloned().collect(); - self.keystroke_matchers.insert( - keystroke_contexts, - KeystrokeMatcher::new(self.keymap.clone()), - ); - } - - if let Some(keystroke_matcher) = self - .keystroke_matchers - .get_mut(self.context_stack.as_slice()) - { - if let KeyMatch::Some(action) = keystroke_matcher.match_keystroke( - &key_down_event.keystroke, - self.context_stack.as_slice(), - ) { - self.dispatch_action_on_node(*node_id, action, cx); - if !cx.propagate_event { - return; - } - } - } - } - - self.context_stack.pop(); - } - } - } - - pub fn dispatch_action(&self, action: Box, cx: &mut WindowContext) { - if let Some(focused_node_id) = self - .focused - .and_then(|focus_id| self.focusable_node_ids.get(&focus_id)) - .copied() - { - self.dispatch_action_on_node(focused_node_id, action, cx); - } - } - - fn dispatch_action_on_node( - &self, - node_id: DispatchNodeId, - action: Box, - cx: &mut WindowContext, - ) { - let dispatch_path = self.dispatch_path(node_id); - - // Capture phase - for node_id in &dispatch_path { - let node = &self.nodes[node_id.0]; - for action_listener in &node.action_listeners { - action_listener(&action, DispatchPhase::Capture, cx); - if !cx.propagate_event { - return; - } - } - } - - // Bubble phase - for node_id in dispatch_path.iter().rev() { - let node = &self.nodes[node_id.0]; - for action_listener in &node.action_listeners { - cx.propagate_event = false; // Actions stop propagation by default during the bubble phase - action_listener(&action, DispatchPhase::Capture, cx); - if !cx.propagate_event { - return; - } - } - } - } -} diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index d88a4119b7..eaac9fc71e 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -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 = StatelessInteractivity, - F: ElementFocus = FocusDisabled, + K: KeyDispatch = NonFocusableKeyDispatch, > { interactivity: I, - focus: F, + key_dispatch: K, children: SmallVec<[AnyElement; 2]>, group: Option, base_style: StyleRefinement, } -pub fn div() -> Div, FocusDisabled> { +pub fn div() -> Div, 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() -> Div, FocusDisabled> { impl Div, F> where V: 'static, - F: ElementFocus, + F: KeyDispatch, { pub fn id(self, id: impl Into) -> Div, 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 Div where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { pub fn group(mut self, group: impl Into) -> Self { self.group = Some(group.into()); @@ -61,6 +65,18 @@ where self } + pub fn context(mut self, context: C) -> Self + where + Self: Sized, + C: TryInto, + 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 Div, FocusDisabled> { - pub fn focusable(self) -> Div, FocusEnabled> { +impl Div, NonFocusableKeyDispatch> { + pub fn focusable(self) -> Div, FocusableKeyDispatch> { Div { interactivity: self.interactivity, - focus: FocusEnabled::new(), + key_dispatch: FocusableKeyDispatch::new(), children: self.children, group: self.group, base_style: self.base_style, @@ -122,10 +138,10 @@ impl Div, FocusDisabled> { pub fn track_focus( self, handle: &FocusHandle, - ) -> Div, FocusEnabled> { + ) -> Div, FocusableKeyDispatch> { Div { interactivity: self.interactivity, - focus: FocusEnabled::tracked(handle), + key_dispatch: FocusableKeyDispatch::tracked(handle), children: self.children, group: self.group, base_style: self.base_style, @@ -149,14 +165,14 @@ impl Div, FocusDisabled> { } } -impl Div, FocusDisabled> { +impl Div, NonFocusableKeyDispatch> { pub fn track_focus( self, handle: &FocusHandle, - ) -> Div, FocusEnabled> { + ) -> Div, FocusableKeyDispatch> { Div { interactivity: self.interactivity.into_stateful(handle), - focus: handle.clone().into(), + key_dispatch: handle.clone().into(), children: self.children, group: self.group, base_style: self.base_style, @@ -164,25 +180,25 @@ impl Div, FocusDisabled> { } } -impl Focusable for Div> +impl Focusable for Div> where V: 'static, I: ElementInteractivity, { fn focus_listeners(&mut self) -> &mut FocusListeners { - &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 Element for Div where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { type ElementState = DivState; @@ -213,14 +229,17 @@ where cx: &mut ViewContext, ) -> 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| { 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 +307,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 +340,7 @@ where impl Component for Div where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { fn render(self) -> AnyElement { AnyElement::new(self) @@ -331,7 +350,7 @@ where impl ParentElement for Div where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { &mut self.children @@ -341,7 +360,7 @@ where impl Styled for Div where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { fn style(&mut self) -> &mut StyleRefinement { &mut self.base_style @@ -351,7 +370,7 @@ where impl StatelessInteractive for Div where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity { self.interactivity.as_stateless_mut() @@ -360,7 +379,7 @@ where impl StatefulInteractive for Div, F> where - F: ElementFocus, + F: KeyDispatch, { fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity { &mut self.interactivity diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 638665d414..1ff088c1af 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -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 = StatelessInteractivity, - F: ElementFocus = FocusDisabled, + F: KeyDispatch = NonFocusableKeyDispatch, > { base: Div, uri: Option, grayscale: bool, } -pub fn img() -> Img, FocusDisabled> { +pub fn img() -> Img, NonFocusableKeyDispatch> { Img { base: div(), uri: None, @@ -29,7 +29,7 @@ impl Img where V: 'static, I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { pub fn uri(mut self, uri: impl Into) -> Self { self.uri = Some(uri.into()); @@ -44,7 +44,7 @@ where impl Img, F> where - F: ElementFocus, + F: KeyDispatch, { pub fn id(self, id: impl Into) -> Img, F> { Img { @@ -58,7 +58,7 @@ where impl Component for Img where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { fn render(self) -> AnyElement { AnyElement::new(self) @@ -68,7 +68,7 @@ where impl Element for Img where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { type ElementState = DivState; @@ -137,7 +137,7 @@ where impl Styled for Img where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { fn style(&mut self) -> &mut StyleRefinement { self.base.style() @@ -147,7 +147,7 @@ where impl StatelessInteractive for Img where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity { self.base.stateless_interactivity() @@ -156,14 +156,14 @@ where impl StatefulInteractive for Img, F> where - F: ElementFocus, + F: KeyDispatch, { fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity { self.base.stateful_interactivity() } } -impl Focusable for Img> +impl Focusable for Img> where V: 'static, I: ElementInteractivity, diff --git a/crates/gpui2/src/elements/svg.rs b/crates/gpui2/src/elements/svg.rs index 8e2ba9d8a1..bafedb7f2d 100644 --- a/crates/gpui2/src/elements/svg.rs +++ b/crates/gpui2/src/elements/svg.rs @@ -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 = StatelessInteractivity, - F: ElementFocus = FocusDisabled, + F: KeyDispatch = NonFocusableKeyDispatch, > { base: Div, path: Option, } -pub fn svg() -> Svg, FocusDisabled> { +pub fn svg() -> Svg, NonFocusableKeyDispatch> { Svg { base: div(), path: None, @@ -25,7 +25,7 @@ pub fn svg() -> Svg, FocusDisabled> { impl Svg where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { pub fn path(mut self, path: impl Into) -> Self { self.path = Some(path.into()); @@ -35,7 +35,7 @@ where impl Svg, F> where - F: ElementFocus, + F: KeyDispatch, { pub fn id(self, id: impl Into) -> Svg, F> { Svg { @@ -48,7 +48,7 @@ where impl Component for Svg where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { fn render(self) -> AnyElement { AnyElement::new(self) @@ -58,7 +58,7 @@ where impl Element for Svg where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { type ElementState = DivState; @@ -108,7 +108,7 @@ where impl Styled for Svg where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { fn style(&mut self) -> &mut StyleRefinement { self.base.style() @@ -118,7 +118,7 @@ where impl StatelessInteractive for Svg where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity { self.base.stateless_interactivity() @@ -128,14 +128,14 @@ where impl StatefulInteractive for Svg, F> where V: 'static, - F: ElementFocus, + F: KeyDispatch, { fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity { self.base.stateful_interactivity() } } -impl Focusable for Svg> +impl Focusable for Svg> where I: ElementInteractivity, { diff --git a/crates/gpui2/src/focusable.rs b/crates/gpui2/src/focusable.rs deleted file mode 100644 index 99f8bb1dd6..0000000000 --- a/crates/gpui2/src/focusable.rs +++ /dev/null @@ -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 = SmallVec<[FocusListener; 2]>; - -pub type FocusListener = - Box) + 'static>; - -pub trait Focusable: Element { - fn focus_listeners(&mut self) -> &mut FocusListeners; - 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) + '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) + '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) + '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) + '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: 'static { - fn as_focusable(&self) -> Option<&FocusEnabled>; - fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled>; - - fn initialize( - &mut self, - focus_handle: Option, - cx: &mut ViewContext, - f: impl FnOnce(Option, &mut ViewContext) -> 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, 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 { - pub focus_handle: Option, - pub focus_listeners: FocusListeners, - pub focus_style: StyleRefinement, - pub focus_in_style: StyleRefinement, - pub in_focus_style: StyleRefinement, -} - -impl FocusEnabled { - 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 ElementFocus for FocusEnabled { - fn as_focusable(&self) -> Option<&FocusEnabled> { - Some(self) - } - - fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled> { - Some(self) - } -} - -impl From for FocusEnabled { - 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 ElementFocus for FocusDisabled { - fn as_focusable(&self) -> Option<&FocusEnabled> { - None - } - - fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled> { - None - } -} diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 42aea446f1..87de7998a8 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -3,17 +3,17 @@ mod action; mod app; mod assets; mod color; -mod dispatch; 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; @@ -42,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; diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 946a59a809..9ac1f56099 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,8 +1,8 @@ use crate::{ - div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds, - Component, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyBindingContext, 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,17 +164,6 @@ pub trait StatelessInteractive: Element { self } - fn context(mut self, context: C) -> Self - where - Self: Sized, - C: TryInto, - 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( mut self, @@ -396,25 +385,6 @@ pub trait ElementInteractivity: 'static { fn as_stateful(&self) -> Option<&StatefulInteractivity>; fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity>; - fn initialize( - &mut self, - cx: &mut ViewContext, - f: impl FnOnce(&mut ViewContext) -> R, - ) -> R { - if let Some(stateful) = self.as_stateful_mut() { - cx.with_element_id(stateful.id.clone(), |global_id, cx| { - 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, @@ -790,7 +760,7 @@ impl ElementInteractivity for StatefulInteractivity { type DropListener = dyn Fn(&mut V, AnyView, &mut ViewContext) + 'static; pub struct StatelessInteractivity { - pub dispatch_context: KeyBindingContext, + pub dispatch_context: KeyContext, pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>, pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>, pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>, @@ -892,7 +862,7 @@ impl InteractiveElementState { impl Default for StatelessInteractivity { fn default() -> Self { Self { - dispatch_context: KeyBindingContext::default(), + dispatch_context: KeyContext::default(), mouse_down_listeners: SmallVec::new(), mouse_up_listeners: SmallVec::new(), mouse_move_listeners: SmallVec::new(), @@ -1236,7 +1206,7 @@ pub type KeyListener = Box< dyn Fn( &mut V, &dyn Any, - &[&KeyBindingContext], + &[&KeyContext], DispatchPhase, &mut ViewContext, ) -> Option> diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs new file mode 100644 index 0000000000..9f76df82c3 --- /dev/null +++ b/crates/gpui2/src/key_dispatch.rs @@ -0,0 +1,547 @@ +use crate::{ + build_action_from_type, Action, Bounds, DispatchPhase, Element, FocusEvent, FocusHandle, + FocusId, KeyContext, KeyDownEvent, KeyMatch, Keymap, 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}, + sync::Arc, +}; +use util::ResultExt; + +type KeyListener = Box; +pub type FocusListeners = SmallVec<[FocusListener; 2]>; +pub type FocusListener = + Box) + 'static>; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub struct DispatchNodeId(usize); + +pub struct KeyDispatcher { + node_stack: Vec, + context_stack: Vec, + nodes: Vec, + focusable_node_ids: HashMap, + keystroke_matchers: HashMap, KeystrokeMatcher>, + keymap: Arc>, +} + +#[derive(Default)] +pub struct DispatchNode { + key_listeners: SmallVec<[KeyListener; 2]>, + action_listeners: SmallVec<[ActionListener; 16]>, + context: KeyContext, + parent: Option, +} + +struct ActionListener { + action_type: TypeId, + listener: Box, +} + +impl KeyDispatcher { + pub fn new(keymap: Arc>) -> 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(); + } + + 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.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: Box, + ) { + self.active_node().action_listeners.push(ActionListener { + 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> { + 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 ActionListener { action_type, .. } in &node.action_listeners { + actions.extend(build_action_from_type(action_type).log_err()); + } + } + } + actions + } + + pub fn dispatch_key(&mut self, target: FocusId, event: &dyn Any, cx: &mut WindowContext) { + if let Some(target_node_id) = self.focusable_node_ids.get(&target).copied() { + self.dispatch_key_on_node(target_node_id, event, cx); + } + } + + fn dispatch_key_on_node( + &mut self, + node_id: DispatchNodeId, + event: &dyn Any, + cx: &mut WindowContext, + ) { + let dispatch_path = self.dispatch_path(node_id); + + // Capture phase + self.context_stack.clear(); + cx.propagate_event = true; + for node_id in &dispatch_path { + let node = &self.nodes[node_id.0]; + if !node.context.is_empty() { + self.context_stack.push(node.context.clone()); + } + + for key_listener in &node.key_listeners { + key_listener(event, DispatchPhase::Capture, cx); + if !cx.propagate_event { + return; + } + } + } + + // Bubble phase + for node_id in dispatch_path.iter().rev() { + let node = &self.nodes[node_id.0]; + + // Handle low level key events + for key_listener in &node.key_listeners { + key_listener(event, DispatchPhase::Bubble, cx); + if !cx.propagate_event { + return; + } + } + + // Match keystrokes + if !node.context.is_empty() { + if let Some(key_down_event) = event.downcast_ref::() { + if !self + .keystroke_matchers + .contains_key(self.context_stack.as_slice()) + { + let keystroke_contexts = self.context_stack.iter().cloned().collect(); + self.keystroke_matchers.insert( + keystroke_contexts, + KeystrokeMatcher::new(self.keymap.clone()), + ); + } + + if let Some(keystroke_matcher) = self + .keystroke_matchers + .get_mut(self.context_stack.as_slice()) + { + if let KeyMatch::Some(action) = keystroke_matcher.match_keystroke( + &key_down_event.keystroke, + self.context_stack.as_slice(), + ) { + self.dispatch_action_on_node(*node_id, action, cx); + if !cx.propagate_event { + return; + } + } + } + } + + self.context_stack.pop(); + } + } + } + + pub fn dispatch_action( + &self, + target: FocusId, + action: Box, + cx: &mut WindowContext, + ) { + if let Some(target_node_id) = self.focusable_node_ids.get(&target).copied() { + self.dispatch_action_on_node(target_node_id, action, cx); + } + } + + fn dispatch_action_on_node( + &self, + node_id: DispatchNodeId, + action: Box, + cx: &mut WindowContext, + ) { + let dispatch_path = self.dispatch_path(node_id); + + // Capture phase + for node_id in &dispatch_path { + let node = &self.nodes[node_id.0]; + for ActionListener { listener, .. } in &node.action_listeners { + listener(&action, DispatchPhase::Capture, cx); + if !cx.propagate_event { + return; + } + } + } + + // Bubble phase + for node_id in dispatch_path.iter().rev() { + let node = &self.nodes[node_id.0]; + for ActionListener { listener, .. } in &node.action_listeners { + cx.propagate_event = false; // Actions stop propagation by default during the bubble phase + listener(&action, DispatchPhase::Capture, cx); + if !cx.propagate_event { + return; + } + } + } + } + + fn active_node(&mut self) -> &mut DispatchNode { + let active_node_id = self.active_node_id(); + &mut self.nodes[active_node_id.0] + } + + fn active_node_id(&self) -> DispatchNodeId { + *self.node_stack.last().unwrap() + } + + /// Returns the DispatchNodeIds from the root of the tree to the given target node id. + 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 trait KeyDispatch: 'static { + fn as_focusable(&self) -> Option<&FocusableKeyDispatch>; + fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch>; + fn key_context(&self) -> &KeyContext; + fn key_context_mut(&mut self) -> &mut KeyContext; + + fn initialize( + &mut self, + focus_handle: Option, + cx: &mut ViewContext, + f: impl FnOnce(Option, &mut ViewContext) -> 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_key_dispatch(self.key_context().clone(), Some(focus_handle), f) + } 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, 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 { + pub key_context: KeyContext, + pub focus_handle: Option, + pub focus_listeners: FocusListeners, + pub focus_style: StyleRefinement, + pub focus_in_style: StyleRefinement, + pub in_focus_style: StyleRefinement, +} + +impl FocusableKeyDispatch { + pub fn new() -> Self { + Self { + key_context: KeyContext::default(), + 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 { + key_context: KeyContext::default(), + focus_handle: Some(handle.clone()), + focus_listeners: FocusListeners::default(), + focus_style: StyleRefinement::default(), + focus_in_style: StyleRefinement::default(), + in_focus_style: StyleRefinement::default(), + } + } +} + +impl KeyDispatch for FocusableKeyDispatch { + fn as_focusable(&self) -> Option<&FocusableKeyDispatch> { + Some(self) + } + + fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch> { + Some(self) + } + + fn key_context(&self) -> &KeyContext { + &self.key_context + } + + fn key_context_mut(&mut self) -> &mut KeyContext { + &mut self.key_context + } +} + +impl From for FocusableKeyDispatch { + fn from(value: FocusHandle) -> Self { + Self { + key_context: KeyContext::default(), + focus_handle: Some(value), + focus_listeners: FocusListeners::default(), + focus_style: StyleRefinement::default(), + focus_in_style: StyleRefinement::default(), + in_focus_style: StyleRefinement::default(), + } + } +} + +#[derive(Default)] +pub struct NonFocusableKeyDispatch { + pub(crate) key_context: KeyContext, +} + +impl KeyDispatch for NonFocusableKeyDispatch { + fn as_focusable(&self) -> Option<&FocusableKeyDispatch> { + None + } + + fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch> { + None + } + + fn key_context(&self) -> &KeyContext { + &self.key_context + } + + fn key_context_mut(&mut self) -> &mut KeyContext { + &mut self.key_context + } +} + +pub trait Focusable: Element { + fn focus_listeners(&mut self) -> &mut FocusListeners; + 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) + '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) + '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) + '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) + '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 + } +} diff --git a/crates/gpui2/src/keymap/binding.rs b/crates/gpui2/src/keymap/binding.rs index 1cf62484b9..9fbd0018b9 100644 --- a/crates/gpui2/src/keymap/binding.rs +++ b/crates/gpui2/src/keymap/binding.rs @@ -1,4 +1,4 @@ -use crate::{Action, KeyBindingContext, KeyBindingContextPredicate, KeyMatch, Keystroke}; +use crate::{Action, KeyBindingContextPredicate, KeyContext, KeyMatch, Keystroke}; use anyhow::Result; use smallvec::SmallVec; @@ -32,7 +32,7 @@ impl KeyBinding { }) } - pub fn matches_context(&self, contexts: &[KeyBindingContext]) -> 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: &[KeyBindingContext], + 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: &[KeyBindingContext], + contexts: &[KeyContext], ) -> Option> { if self.action.partial_eq(action) && self.matches_context(contexts) { Some(self.keystrokes.clone()) diff --git a/crates/gpui2/src/keymap/context.rs b/crates/gpui2/src/keymap/context.rs index 834bd4989a..b0225e73e7 100644 --- a/crates/gpui2/src/keymap/context.rs +++ b/crates/gpui2/src/keymap/context.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use smallvec::SmallVec; #[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] -pub struct KeyBindingContext(SmallVec<[ContextEntry; 8]>); +pub struct KeyContext(SmallVec<[ContextEntry; 8]>); #[derive(Clone, Debug, Eq, PartialEq, Hash)] struct ContextEntry { @@ -11,7 +11,7 @@ struct ContextEntry { value: Option, } -impl<'a> TryFrom<&'a str> for KeyBindingContext { +impl<'a> TryFrom<&'a str> for KeyContext { type Error = anyhow::Error; fn try_from(value: &'a str) -> Result { @@ -19,7 +19,7 @@ impl<'a> TryFrom<&'a str> for KeyBindingContext { } } -impl KeyBindingContext { +impl KeyContext { pub fn parse(source: &str) -> Result { let mut context = Self::default(); let source = skip_whitespace(source); @@ -130,7 +130,7 @@ impl KeyBindingContextPredicate { } } - pub fn eval(&self, contexts: &[KeyBindingContext]) -> bool { + pub fn eval(&self, contexts: &[KeyContext]) -> bool { let Some(context) = contexts.last() else { return false; }; @@ -293,19 +293,16 @@ mod tests { #[test] fn test_parse_context() { - let mut expected = KeyBindingContext::default(); + let mut expected = KeyContext::default(); expected.set("foo", "bar"); expected.add("baz"); - assert_eq!(KeyBindingContext::parse("baz foo=bar").unwrap(), expected); - assert_eq!(KeyBindingContext::parse("foo = bar baz").unwrap(), expected); + assert_eq!(KeyContext::parse("baz foo=bar").unwrap(), expected); + assert_eq!(KeyContext::parse("foo = bar baz").unwrap(), expected); assert_eq!( - KeyBindingContext::parse(" baz foo = bar baz").unwrap(), - expected - ); - assert_eq!( - KeyBindingContext::parse(" foo = bar baz").unwrap(), + KeyContext::parse(" baz foo = bar baz").unwrap(), expected ); + assert_eq!(KeyContext::parse(" foo = bar baz").unwrap(), expected); } #[test] diff --git a/crates/gpui2/src/keymap/matcher.rs b/crates/gpui2/src/keymap/matcher.rs index c9b5d26ecb..bab9c0f575 100644 --- a/crates/gpui2/src/keymap/matcher.rs +++ b/crates/gpui2/src/keymap/matcher.rs @@ -1,4 +1,4 @@ -use crate::{Action, KeyBindingContext, Keymap, KeymapVersion, Keystroke}; +use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke}; use parking_lot::Mutex; use smallvec::SmallVec; use std::sync::Arc; @@ -44,7 +44,7 @@ impl KeystrokeMatcher { pub fn match_keystroke( &mut self, keystroke: &Keystroke, - context_stack: &[KeyBindingContext], + 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 KeystrokeMatcher { pub fn keystrokes_for_action( &self, action: &dyn Action, - contexts: &[KeyBindingContext], + contexts: &[KeyContext], ) -> Option> { self.keymap .lock() diff --git a/crates/gpui2/src/prelude.rs b/crates/gpui2/src/prelude.rs new file mode 100644 index 0000000000..bc998fc1f4 --- /dev/null +++ b/crates/gpui2/src/prelude.rs @@ -0,0 +1 @@ +pub use crate::{Context, ParentElement, Refineable}; diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 00e1e55cd5..d12d84f43b 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -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 Component for AnyView { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index cde7b31754..15a59253e7 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1,15 +1,14 @@ use crate::{ - build_action_from_type, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, - AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, - DevicePixels, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, - FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, - KeyBindingContext, KeyListener, KeyMatch, Keystroke, KeystrokeMatcher, LayoutId, Model, - ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, - MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, - PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, - RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, - Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, - VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, + Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DisplayId, Edges, Effect, + Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, + Hsla, ImageData, InputEvent, IsZero, KeyContext, KeyDispatcher, LayoutId, Model, ModelContext, + Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, + Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, + PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, + RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, + Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, + WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -60,16 +59,7 @@ pub enum DispatchPhase { } type AnyObserver = Box bool + 'static>; -type AnyListener = Box; -type AnyKeyListener = Box< - dyn Fn( - &dyn Any, - &[&KeyBindingContext], - DispatchPhase, - &mut WindowContext, - ) -> Option> - + 'static, ->; +type AnyMouseListener = Box; type AnyFocusListener = Box; type AnyWindowFocusListener = Box bool + 'static>; @@ -97,20 +87,12 @@ 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 + .key_dispatcher + .as_ref() + .unwrap() + .focus_contains(*self, other) } } @@ -227,20 +209,31 @@ pub struct Window { pub(crate) focus: Option, } -#[derive(Default)] +// #[derive(Default)] pub(crate) struct Frame { element_states: HashMap, - key_matchers: HashMap, - mouse_listeners: HashMap>, + mouse_listeners: HashMap>, + pub(crate) key_dispatcher: Option, pub(crate) focus_listeners: Vec, - pub(crate) key_dispatch_stack: Vec, - freeze_key_dispatch_stack: bool, - focus_parents_by_child: HashMap, pub(crate) scene_builder: SceneBuilder, z_index_stack: StackingOrder, content_mask_stack: Vec>, element_offset_stack: Vec>, - focus_stack: Vec, +} + +impl Frame { + pub fn new(key_dispatcher: KeyDispatcher) -> Self { + Frame { + element_states: HashMap::default(), + mouse_listeners: HashMap::default(), + key_dispatcher: Some(key_dispatcher), + 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 +302,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(KeyDispatcher::new(cx.keymap.clone())), + current_frame: Frame::new(KeyDispatcher::new(cx.keymap.clone())), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), default_prevented: true, @@ -328,18 +321,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(KeyBindingContext), -} - /// 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,7 +388,9 @@ 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) { + let focus_id = handle.id; + + if self.window.focus == Some(focus_id) { return; } @@ -415,13 +398,10 @@ impl<'a> WindowContext<'a> { 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 +421,13 @@ impl<'a> WindowContext<'a> { } pub fn dispatch_action(&mut self, action: Box) { - 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| { + let dispatcher = cx.window.current_frame.key_dispatcher.take().unwrap(); + dispatcher.dispatch_action(focus_handle.id, action, cx); + cx.window.current_frame.key_dispatcher = Some(dispatcher); + }) + } } /// Schedules the given function to be run at the end of the current effect cycle, allowing entities @@ -1079,26 +1061,6 @@ impl<'a> WindowContext<'a> { self.window.dirty = false; } - pub(crate) fn dispatch_stack(&mut self) -> Vec { - 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 +1072,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.key_dispatcher.as_mut().map(KeyDispatcher::clear); } /// Dispatch a mouse or keyboard event on the window. @@ -1226,99 +1185,16 @@ impl<'a> WindowContext<'a> { .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::<[&KeyBindingContext; 16]>::new(); - - for (ix, frame) in key_dispatch_stack.iter().enumerate() { - match frame { - KeyDispatchStackFrame::Listener { - event_type, - listener, - } => { - if key_event_type == *event_type { - if let Some(action) = listener( - any_key_event, - &context_stack, - DispatchPhase::Capture, - self, - ) { - self.dispatch_action_internal(action, &key_dispatch_stack[..ix]); - } - if !self.app.propagate_event { - break; - } - } - } - KeyDispatchStackFrame::Context(context) => { - context_stack.push(&context); - } - } + if let Some(focus_id) = self.window.focus { + let mut dispatcher = self.window.current_frame.key_dispatcher.take().unwrap(); + dispatcher.dispatch_key(focus_id, any_key_event, self); + self.window.current_frame.key_dispatcher = Some(dispatcher); } - - if self.app.propagate_event { - for (ix, frame) in key_dispatch_stack.iter().enumerate().rev() { - match frame { - KeyDispatchStackFrame::Listener { - event_type, - listener, - } => { - if key_event_type == *event_type { - if let Some(action) = listener( - any_key_event, - &context_stack, - DispatchPhase::Bubble, - self, - ) { - self.dispatch_action_internal( - action, - &key_dispatch_stack[..ix], - ); - } - - if !self.app.propagate_event { - break; - } - } - } - KeyDispatchStackFrame::Context(_) => { - context_stack.pop(); - } - } - } - } - - drop(context_stack); - self.window.current_frame.key_dispatch_stack = key_dispatch_stack; } !self.app.propagate_event } - /// 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: &[KeyBindingContext], - ) -> KeyMatch { - let key_match = 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(); - } - } - - key_match - } - /// Register the given handler to be invoked whenever the global of the given type /// is updated. pub fn observe_global( @@ -1345,105 +1221,16 @@ impl<'a> WindowContext<'a> { self.window.platform_window.prompt(level, msg, answers) } - pub fn available_actions(&self) -> impl Iterator> + '_ { - 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, - 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> { + if let Some(focus_id) = self.window.focus { + self.window + .current_frame + .key_dispatcher + .as_ref() + .unwrap() + .available_actions(focus_id) + } else { + Vec::new() } } } @@ -1609,22 +1396,9 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { id: impl Into, 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(|| KeystrokeMatcher::new(keymap)), - ); - } - let result = f(global_id, self); let window: &mut Window = self.borrow_mut(); window.element_id_stack.pop(); @@ -2109,94 +1883,25 @@ impl<'a, V: 'static> ViewContext<'a, V> { })); } - pub fn with_key_listeners( + pub fn with_key_dispatch( &mut self, - key_listeners: impl IntoIterator)>, - f: impl FnOnce(&mut Self) -> R, + context: KeyContext, + focus_handle: Option, + f: impl FnOnce(Option, &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: &[&KeyBindingContext], - 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 mut old_dispatcher = self.window.previous_frame.key_dispatcher.take().unwrap(); + let mut current_dispatcher = self.window.current_frame.key_dispatcher.take().unwrap(); + + current_dispatcher.push_node(context, &mut old_dispatcher); + if let Some(focus_handle) = focus_handle.as_ref() { + current_dispatcher.make_focusable(focus_handle.id); } + let result = f(focus_handle, self); + current_dispatcher.pop_node(); - let result = f(self); + self.window.previous_frame.key_dispatcher = Some(old_dispatcher); + self.window.current_frame.key_dispatcher = Some(current_dispatcher); - 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( - &mut self, - context: KeyBindingContext, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - if context.is_empty() { - return f(self); - } - - if !self.window.current_frame.freeze_key_dispatch_stack { - self.window - .current_frame - .key_dispatch_stack - .push(KeyDispatchStackFrame::Context(context)); - } - - let result = f(self); - - if !self.window.previous_frame.freeze_key_dispatch_stack { - self.window.previous_frame.key_dispatch_stack.pop(); - } - - result - } - - pub fn with_focus( - &mut self, - focus_handle: FocusHandle, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - if let Some(parent_focus_id) = self.window.current_frame.focus_stack.last().copied() { - self.window - .current_frame - .focus_parents_by_child - .insert(focus_handle.id, parent_focus_id); - } - self.window.current_frame.focus_stack.push(focus_handle.id); - - if Some(focus_handle.id) == self.window.focus { - self.window.current_frame.freeze_key_dispatch_stack = true; - } - - let result = f(self); - - self.window.current_frame.focus_stack.pop(); result } diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 9d0019b2dc..62c5308dec 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -1,8 +1,8 @@ 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, FocusableKeyDispatch, ParentElement, Render, + StatefulInteractivity, StatelessInteractive, Styled, Task, UniformListScrollHandle, View, + ViewContext, VisualContext, WindowContext, }; use std::cmp; use theme::ActiveTheme; @@ -137,7 +137,7 @@ impl Picker { } impl Render for Picker { - type Element = Div, FocusEnabled>; + type Element = Div, FocusableKeyDispatch>; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { div() diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index 984ee421db..368fb20fbf 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -1,5 +1,5 @@ use gpui::{ - actions, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render, + actions, div, Div, Focusable, FocusableKeyDispatch, KeyBinding, ParentElement, Render, StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext, WindowContext, }; use theme2::ActiveTheme; @@ -21,7 +21,7 @@ impl FocusStory { } impl Render for FocusStory { - type Element = Div, FocusEnabled>; + type Element = Div, FocusableKeyDispatch>; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { let theme = cx.theme(); diff --git a/crates/ui2/src/styled_ext.rs b/crates/ui2/src/styled_ext.rs index 543781ef52..980b31fe5d 100644 --- a/crates/ui2/src/styled_ext.rs +++ b/crates/ui2/src/styled_ext.rs @@ -1,4 +1,4 @@ -use gpui::{Div, ElementFocus, ElementInteractivity, Styled}; +use gpui::{Div, ElementInteractivity, KeyDispatch, Styled}; use crate::UITextSize; @@ -69,6 +69,6 @@ pub trait StyledExt: Styled { impl StyledExt for Div where I: ElementInteractivity, - F: ElementFocus, + F: KeyDispatch, { } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 1522b4ec4e..e55d59303d 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -38,10 +38,10 @@ use futures::{ use gpui::{ actions, div, point, rems, size, Action, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter, - FocusHandle, GlobalPixels, KeyBindingContext, Model, ModelContext, ParentElement, Point, - Render, Size, StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled, - Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, - WindowHandle, WindowOptions, + FocusHandle, GlobalPixels, KeyContext, Model, ModelContext, ParentElement, Point, Render, Size, + StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled, Subscription, Task, + View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, + WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; @@ -3743,158 +3743,158 @@ impl Render for Workspace { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let mut context = KeyBindingContext::default(); + let mut context = KeyContext::default(); context.add("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()), - // ), + + 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? + 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("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::::new("Toggle User Settings").on_click( - // Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)), - // )) - // .child( - // Button::::new("Toggle Toasts").on_click(Arc::new( - // |workspace, cx| workspace.debug_toggle_toast(cx), - // )), - // ) - // .child( - // Button::::new("Toggle Livestream").on_click(Arc::new( - // |workspace, cx| workspace.debug_toggle_livestream(cx), - // )), - // ) - // }) - // .child( - // Button::::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::::new("Toggle User Settings").on_click( + // Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)), + // )) + // .child( + // Button::::new("Toggle Toasts").on_click(Arc::new( + // |workspace, cx| workspace.debug_toggle_toast(cx), + // )), + // ) + // .child( + // Button::::new("Toggle Livestream").on_click(Arc::new( + // |workspace, cx| workspace.debug_toggle_livestream(cx), + // )), + // ) + // }) + // .child( + // Button::::new("Toggle Debug") + // .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))), + // ), + ) } } // todo!() From 318cb784b2e7fbaca99504d879a208a18590c97c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 13 Nov 2023 10:17:52 +0100 Subject: [PATCH 03/15] Fix panic when calling `with_key_dispatch` recursively Co-Authored-By: Thorsten --- crates/gpui2/src/window.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 15a59253e7..3da2664f79 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1889,18 +1889,18 @@ impl<'a, V: 'static> ViewContext<'a, V> { focus_handle: Option, f: impl FnOnce(Option, &mut Self) -> R, ) -> R { - let mut old_dispatcher = self.window.previous_frame.key_dispatcher.take().unwrap(); - let mut current_dispatcher = self.window.current_frame.key_dispatcher.take().unwrap(); + let window = &mut self.window; + let old_dispatcher = window.previous_frame.key_dispatcher.as_mut().unwrap(); + let current_dispatcher = window.current_frame.key_dispatcher.as_mut().unwrap(); - current_dispatcher.push_node(context, &mut old_dispatcher); + current_dispatcher.push_node(context, old_dispatcher); if let Some(focus_handle) = focus_handle.as_ref() { current_dispatcher.make_focusable(focus_handle.id); } let result = f(focus_handle, self); - current_dispatcher.pop_node(); - self.window.previous_frame.key_dispatcher = Some(old_dispatcher); - self.window.current_frame.key_dispatcher = Some(current_dispatcher); + let current_dispatcher = self.window.current_frame.key_dispatcher.as_mut().unwrap(); + current_dispatcher.pop_node(); result } From 9c182538638ad82690342ef4f8598e8349a3d1bb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 13 Nov 2023 11:37:57 +0100 Subject: [PATCH 04/15] Register key and action listeners using `Interactive::initialize` Co-Authored-By: Thorsten --- crates/editor2/src/element.rs | 362 ++++++++++++------------- crates/gpui2/src/elements/div.rs | 1 + crates/gpui2/src/interactive.rs | 87 +++--- crates/gpui2/src/key_dispatch.rs | 50 ++-- crates/gpui2/src/window.rs | 62 +++++ crates/storybook2/src/stories/focus.rs | 6 +- 6 files changed, 317 insertions(+), 251 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index d42a14bb77..b273c5914a 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -15,10 +15,10 @@ use crate::{ use anyhow::Result; use collections::{BTreeMap, HashMap}; use gpui::{ - black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, - BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, - ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler, - KeyContext, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton, + black, hsla, point, px, relative, size, transparent_black, Action, ActionListener, AnyElement, + AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, + Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, + InputHandler, KeyContext, KeyDownEvent, KeyMatch, Line, LineLayout, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size, Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout, }; @@ -2459,7 +2459,166 @@ impl Element for EditorElement { cx.with_key_dispatch( dispatch_context, Some(editor.focus_handle.clone()), - |_, _| {}, + |_, cx| { + handle_action(cx, Editor::move_left); + handle_action(cx, Editor::move_right); + handle_action(cx, Editor::move_down); + handle_action(cx, Editor::move_up); + // on_action(cx, Editor::new_file); todo!() + // on_action(cx, Editor::new_file_in_direction); todo!() + handle_action(cx, Editor::cancel); + handle_action(cx, Editor::newline); + handle_action(cx, Editor::newline_above); + handle_action(cx, Editor::newline_below); + handle_action(cx, Editor::backspace); + handle_action(cx, Editor::delete); + handle_action(cx, Editor::tab); + handle_action(cx, Editor::tab_prev); + handle_action(cx, Editor::indent); + handle_action(cx, Editor::outdent); + handle_action(cx, Editor::delete_line); + handle_action(cx, Editor::join_lines); + handle_action(cx, Editor::sort_lines_case_sensitive); + handle_action(cx, Editor::sort_lines_case_insensitive); + handle_action(cx, Editor::reverse_lines); + handle_action(cx, Editor::shuffle_lines); + handle_action(cx, Editor::convert_to_upper_case); + handle_action(cx, Editor::convert_to_lower_case); + handle_action(cx, Editor::convert_to_title_case); + handle_action(cx, Editor::convert_to_snake_case); + handle_action(cx, Editor::convert_to_kebab_case); + handle_action(cx, Editor::convert_to_upper_camel_case); + handle_action(cx, Editor::convert_to_lower_camel_case); + handle_action(cx, Editor::delete_to_previous_word_start); + handle_action(cx, Editor::delete_to_previous_subword_start); + handle_action(cx, Editor::delete_to_next_word_end); + handle_action(cx, Editor::delete_to_next_subword_end); + handle_action(cx, Editor::delete_to_beginning_of_line); + handle_action(cx, Editor::delete_to_end_of_line); + handle_action(cx, Editor::cut_to_end_of_line); + handle_action(cx, Editor::duplicate_line); + handle_action(cx, Editor::move_line_up); + handle_action(cx, Editor::move_line_down); + handle_action(cx, Editor::transpose); + handle_action(cx, Editor::cut); + handle_action(cx, Editor::copy); + handle_action(cx, Editor::paste); + handle_action(cx, Editor::undo); + handle_action(cx, Editor::redo); + handle_action(cx, Editor::move_page_up); + handle_action(cx, Editor::move_page_down); + handle_action(cx, Editor::next_screen); + handle_action(cx, Editor::scroll_cursor_top); + handle_action(cx, Editor::scroll_cursor_center); + handle_action(cx, Editor::scroll_cursor_bottom); + handle_action(cx, |editor, _: &LineDown, cx| { + editor.scroll_screen(&ScrollAmount::Line(1.), cx) + }); + handle_action(cx, |editor, _: &LineUp, cx| { + editor.scroll_screen(&ScrollAmount::Line(-1.), cx) + }); + handle_action(cx, |editor, _: &HalfPageDown, cx| { + editor.scroll_screen(&ScrollAmount::Page(0.5), cx) + }); + handle_action(cx, |editor, _: &HalfPageUp, cx| { + editor.scroll_screen(&ScrollAmount::Page(-0.5), cx) + }); + handle_action(cx, |editor, _: &PageDown, cx| { + editor.scroll_screen(&ScrollAmount::Page(1.), cx) + }); + handle_action(cx, |editor, _: &PageUp, cx| { + editor.scroll_screen(&ScrollAmount::Page(-1.), cx) + }); + handle_action(cx, Editor::move_to_previous_word_start); + handle_action(cx, Editor::move_to_previous_subword_start); + handle_action(cx, Editor::move_to_next_word_end); + handle_action(cx, Editor::move_to_next_subword_end); + handle_action(cx, Editor::move_to_beginning_of_line); + handle_action(cx, Editor::move_to_end_of_line); + handle_action(cx, Editor::move_to_start_of_paragraph); + handle_action(cx, Editor::move_to_end_of_paragraph); + handle_action(cx, Editor::move_to_beginning); + handle_action(cx, Editor::move_to_end); + handle_action(cx, Editor::select_up); + handle_action(cx, Editor::select_down); + handle_action(cx, Editor::select_left); + handle_action(cx, Editor::select_right); + handle_action(cx, Editor::select_to_previous_word_start); + handle_action(cx, Editor::select_to_previous_subword_start); + handle_action(cx, Editor::select_to_next_word_end); + handle_action(cx, Editor::select_to_next_subword_end); + handle_action(cx, Editor::select_to_beginning_of_line); + handle_action(cx, Editor::select_to_end_of_line); + handle_action(cx, Editor::select_to_start_of_paragraph); + handle_action(cx, Editor::select_to_end_of_paragraph); + handle_action(cx, Editor::select_to_beginning); + handle_action(cx, Editor::select_to_end); + handle_action(cx, Editor::select_all); + handle_action(cx, |editor, action, cx| { + editor.select_all_matches(action, cx).log_err(); + }); + handle_action(cx, Editor::select_line); + handle_action(cx, Editor::split_selection_into_lines); + handle_action(cx, Editor::add_selection_above); + handle_action(cx, Editor::add_selection_below); + handle_action(cx, |editor, action, cx| { + editor.select_next(action, cx).log_err(); + }); + handle_action(cx, |editor, action, cx| { + editor.select_previous(action, cx).log_err(); + }); + handle_action(cx, Editor::toggle_comments); + handle_action(cx, Editor::select_larger_syntax_node); + handle_action(cx, Editor::select_smaller_syntax_node); + handle_action(cx, Editor::move_to_enclosing_bracket); + handle_action(cx, Editor::undo_selection); + handle_action(cx, Editor::redo_selection); + handle_action(cx, Editor::go_to_diagnostic); + handle_action(cx, Editor::go_to_prev_diagnostic); + handle_action(cx, Editor::go_to_hunk); + handle_action(cx, Editor::go_to_prev_hunk); + handle_action(cx, Editor::go_to_definition); + handle_action(cx, Editor::go_to_definition_split); + handle_action(cx, Editor::go_to_type_definition); + handle_action(cx, Editor::go_to_type_definition_split); + handle_action(cx, Editor::fold); + handle_action(cx, Editor::fold_at); + handle_action(cx, Editor::unfold_lines); + handle_action(cx, Editor::unfold_at); + handle_action(cx, Editor::fold_selected_ranges); + handle_action(cx, Editor::show_completions); + handle_action(cx, Editor::toggle_code_actions); + // on_action(cx, Editor::open_excerpts); todo!() + handle_action(cx, Editor::toggle_soft_wrap); + handle_action(cx, Editor::toggle_inlay_hints); + handle_action(cx, Editor::reveal_in_finder); + handle_action(cx, Editor::copy_path); + handle_action(cx, Editor::copy_relative_path); + handle_action(cx, Editor::copy_highlight_json); + handle_action(cx, |editor, action, cx| { + editor + .format(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }); + handle_action(cx, Editor::restart_language_server); + handle_action(cx, Editor::show_character_palette); + // on_action(cx, Editor::confirm_completion); todo!() + handle_action(cx, |editor, action, cx| { + editor + .confirm_code_action(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }); + // on_action(cx, Editor::rename); todo!() + // on_action(cx, Editor::confirm_rename); todo!() + // on_action(cx, Editor::find_all_references); todo!() + handle_action(cx, Editor::next_copilot_suggestion); + handle_action(cx, Editor::previous_copilot_suggestion); + handle_action(cx, Editor::copilot_suggest); + handle_action(cx, Editor::context_menu_first); + handle_action(cx, Editor::context_menu_prev); + handle_action(cx, Editor::context_menu_next); + handle_action(cx, Editor::context_menu_last); + }, ) }); } @@ -3995,197 +4154,14 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 { // } // } -fn build_key_listeners( - global_element_id: GlobalElementId, -) -> impl IntoIterator)> { - [ - build_action_listener(Editor::move_left), - build_action_listener(Editor::move_right), - build_action_listener(Editor::move_down), - build_action_listener(Editor::move_up), - // build_action_listener(Editor::new_file), todo!() - // build_action_listener(Editor::new_file_in_direction), todo!() - build_action_listener(Editor::cancel), - build_action_listener(Editor::newline), - build_action_listener(Editor::newline_above), - build_action_listener(Editor::newline_below), - build_action_listener(Editor::backspace), - build_action_listener(Editor::delete), - build_action_listener(Editor::tab), - build_action_listener(Editor::tab_prev), - build_action_listener(Editor::indent), - build_action_listener(Editor::outdent), - build_action_listener(Editor::delete_line), - build_action_listener(Editor::join_lines), - build_action_listener(Editor::sort_lines_case_sensitive), - build_action_listener(Editor::sort_lines_case_insensitive), - build_action_listener(Editor::reverse_lines), - build_action_listener(Editor::shuffle_lines), - build_action_listener(Editor::convert_to_upper_case), - build_action_listener(Editor::convert_to_lower_case), - build_action_listener(Editor::convert_to_title_case), - build_action_listener(Editor::convert_to_snake_case), - build_action_listener(Editor::convert_to_kebab_case), - build_action_listener(Editor::convert_to_upper_camel_case), - build_action_listener(Editor::convert_to_lower_camel_case), - build_action_listener(Editor::delete_to_previous_word_start), - build_action_listener(Editor::delete_to_previous_subword_start), - build_action_listener(Editor::delete_to_next_word_end), - build_action_listener(Editor::delete_to_next_subword_end), - build_action_listener(Editor::delete_to_beginning_of_line), - build_action_listener(Editor::delete_to_end_of_line), - build_action_listener(Editor::cut_to_end_of_line), - build_action_listener(Editor::duplicate_line), - build_action_listener(Editor::move_line_up), - build_action_listener(Editor::move_line_down), - build_action_listener(Editor::transpose), - build_action_listener(Editor::cut), - build_action_listener(Editor::copy), - build_action_listener(Editor::paste), - build_action_listener(Editor::undo), - build_action_listener(Editor::redo), - build_action_listener(Editor::move_page_up), - build_action_listener(Editor::move_page_down), - build_action_listener(Editor::next_screen), - build_action_listener(Editor::scroll_cursor_top), - build_action_listener(Editor::scroll_cursor_center), - build_action_listener(Editor::scroll_cursor_bottom), - build_action_listener(|editor, _: &LineDown, cx| { - editor.scroll_screen(&ScrollAmount::Line(1.), cx) - }), - build_action_listener(|editor, _: &LineUp, cx| { - editor.scroll_screen(&ScrollAmount::Line(-1.), cx) - }), - build_action_listener(|editor, _: &HalfPageDown, cx| { - editor.scroll_screen(&ScrollAmount::Page(0.5), cx) - }), - build_action_listener(|editor, _: &HalfPageUp, cx| { - editor.scroll_screen(&ScrollAmount::Page(-0.5), cx) - }), - build_action_listener(|editor, _: &PageDown, cx| { - editor.scroll_screen(&ScrollAmount::Page(1.), cx) - }), - build_action_listener(|editor, _: &PageUp, cx| { - editor.scroll_screen(&ScrollAmount::Page(-1.), cx) - }), - build_action_listener(Editor::move_to_previous_word_start), - build_action_listener(Editor::move_to_previous_subword_start), - build_action_listener(Editor::move_to_next_word_end), - build_action_listener(Editor::move_to_next_subword_end), - build_action_listener(Editor::move_to_beginning_of_line), - build_action_listener(Editor::move_to_end_of_line), - build_action_listener(Editor::move_to_start_of_paragraph), - build_action_listener(Editor::move_to_end_of_paragraph), - build_action_listener(Editor::move_to_beginning), - build_action_listener(Editor::move_to_end), - build_action_listener(Editor::select_up), - build_action_listener(Editor::select_down), - build_action_listener(Editor::select_left), - build_action_listener(Editor::select_right), - build_action_listener(Editor::select_to_previous_word_start), - build_action_listener(Editor::select_to_previous_subword_start), - build_action_listener(Editor::select_to_next_word_end), - build_action_listener(Editor::select_to_next_subword_end), - build_action_listener(Editor::select_to_beginning_of_line), - build_action_listener(Editor::select_to_end_of_line), - build_action_listener(Editor::select_to_start_of_paragraph), - build_action_listener(Editor::select_to_end_of_paragraph), - build_action_listener(Editor::select_to_beginning), - build_action_listener(Editor::select_to_end), - build_action_listener(Editor::select_all), - build_action_listener(|editor, action, cx| { - editor.select_all_matches(action, cx).log_err(); - }), - build_action_listener(Editor::select_line), - build_action_listener(Editor::split_selection_into_lines), - build_action_listener(Editor::add_selection_above), - build_action_listener(Editor::add_selection_below), - build_action_listener(|editor, action, cx| { - editor.select_next(action, cx).log_err(); - }), - build_action_listener(|editor, action, cx| { - editor.select_previous(action, cx).log_err(); - }), - build_action_listener(Editor::toggle_comments), - build_action_listener(Editor::select_larger_syntax_node), - build_action_listener(Editor::select_smaller_syntax_node), - build_action_listener(Editor::move_to_enclosing_bracket), - build_action_listener(Editor::undo_selection), - build_action_listener(Editor::redo_selection), - build_action_listener(Editor::go_to_diagnostic), - build_action_listener(Editor::go_to_prev_diagnostic), - build_action_listener(Editor::go_to_hunk), - build_action_listener(Editor::go_to_prev_hunk), - build_action_listener(Editor::go_to_definition), - build_action_listener(Editor::go_to_definition_split), - build_action_listener(Editor::go_to_type_definition), - build_action_listener(Editor::go_to_type_definition_split), - build_action_listener(Editor::fold), - build_action_listener(Editor::fold_at), - build_action_listener(Editor::unfold_lines), - build_action_listener(Editor::unfold_at), - build_action_listener(Editor::fold_selected_ranges), - build_action_listener(Editor::show_completions), - build_action_listener(Editor::toggle_code_actions), - // build_action_listener(Editor::open_excerpts), todo!() - build_action_listener(Editor::toggle_soft_wrap), - build_action_listener(Editor::toggle_inlay_hints), - build_action_listener(Editor::reveal_in_finder), - build_action_listener(Editor::copy_path), - build_action_listener(Editor::copy_relative_path), - build_action_listener(Editor::copy_highlight_json), - build_action_listener(|editor, action, cx| { - editor - .format(action, cx) - .map(|task| task.detach_and_log_err(cx)); - }), - build_action_listener(Editor::restart_language_server), - build_action_listener(Editor::show_character_palette), - // build_action_listener(Editor::confirm_completion), todo!() - build_action_listener(|editor, action, cx| { - editor - .confirm_code_action(action, cx) - .map(|task| task.detach_and_log_err(cx)); - }), - // build_action_listener(Editor::rename), todo!() - // build_action_listener(Editor::confirm_rename), todo!() - // build_action_listener(Editor::find_all_references), todo!() - build_action_listener(Editor::next_copilot_suggestion), - build_action_listener(Editor::previous_copilot_suggestion), - build_action_listener(Editor::copilot_suggest), - build_action_listener(Editor::context_menu_first), - build_action_listener(Editor::context_menu_prev), - build_action_listener(Editor::context_menu_next), - build_action_listener(Editor::context_menu_last), - ] -} - -fn build_key_listener( - listener: impl Fn( - &mut Editor, - &T, - &[&KeyContext], - DispatchPhase, - &mut ViewContext, - ) -> Option> - + 'static, -) -> (TypeId, KeyListener) { - ( - TypeId::of::(), - Box::new(move |editor, event, dispatch_context, phase, cx| { - let key_event = event.downcast_ref::()?; - listener(editor, key_event, dispatch_context, phase, cx) - }), - ) -} - -fn build_action_listener( +fn handle_action( + cx: &mut ViewContext, listener: impl Fn(&mut Editor, &T, &mut ViewContext) + 'static, -) -> (TypeId, KeyListener) { - build_key_listener(move |editor, action: &T, dispatch_context, phase, cx| { +) { + cx.on_action(TypeId::of::(), move |editor, action, phase, cx| { + let action = action.downcast_ref().unwrap(); if phase == DispatchPhase::Bubble { listener(editor, action, cx); } - None }) } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index eaac9fc71e..7bfd4b244a 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -234,6 +234,7 @@ where element_state.focus_handle.take(), cx, |focus_handle, cx| { + this.interactivity.initialize(cx); element_state.focus_handle = focus_handle; for child in &mut this.children { child.initialize(view_state, cx); diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 9ac1f56099..4a7633f8dc 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -165,43 +165,40 @@ pub trait StatelessInteractive: Element { } /// Capture the given action, fires during the capture phase - fn capture_action( + fn capture_action( mut self, listener: impl Fn(&mut V, &A, &mut ViewContext) + 'static, ) -> Self where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( + self.stateless_interactivity().action_listeners.push(( TypeId::of::(), - Box::new(move |view, action, _dipatch_context, phase, cx| { + Box::new(move |view, action, phase, cx| { let action = action.downcast_ref().unwrap(); if phase == DispatchPhase::Capture { listener(view, action, cx) } - None }), )); self } /// Add a listener for the given action, fires during the bubble event phase - fn on_action( + fn on_action( mut self, listener: impl Fn(&mut V, &A, &mut ViewContext) + 'static, ) -> Self where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( + self.stateless_interactivity().action_listeners.push(( TypeId::of::(), - Box::new(move |view, action, _dispatch_context, phase, cx| { + Box::new(move |view, action, phase, cx| { let action = action.downcast_ref().unwrap(); if phase == DispatchPhase::Bubble { listener(view, action, cx) } - - None }), )); self @@ -214,14 +211,11 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( - TypeId::of::(), - Box::new(move |view, event, _, phase, cx| { - let event = event.downcast_ref().unwrap(); - listener(view, event, phase, cx); - None - }), - )); + self.stateless_interactivity() + .key_down_listeners + .push(Box::new(move |view, event, phase, cx| { + listener(view, event, phase, cx) + })); self } @@ -232,14 +226,11 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( - TypeId::of::(), - Box::new(move |view, event, _, phase, cx| { - let event = event.downcast_ref().unwrap(); - listener(view, event, phase, cx); - None - }), - )); + self.stateless_interactivity() + .key_up_listeners + .push(Box::new(move |view, event, phase, cx| { + listener(view, event, phase, cx) + })); self } @@ -439,6 +430,26 @@ pub trait ElementInteractivity: 'static { } } + fn initialize(&mut self, cx: &mut ViewContext) { + let stateless = self.as_stateless_mut(); + + for listener in stateless.key_down_listeners.drain(..) { + cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| { + listener(state, event, phase, cx); + }) + } + + for listener in stateless.key_up_listeners.drain(..) { + cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| { + listener(state, event, phase, cx); + }) + } + + for (action_type, listener) in stateless.action_listeners.drain(..) { + cx.on_action(action_type, listener) + } + } + fn paint( &mut self, bounds: Bounds, @@ -765,7 +776,9 @@ pub struct StatelessInteractivity { pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>, pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>, pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener; 2]>, - pub key_listeners: SmallVec<[(TypeId, KeyListener); 32]>, + pub key_down_listeners: SmallVec<[KeyDownListener; 2]>, + pub key_up_listeners: SmallVec<[KeyUpListener; 2]>, + pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>, pub hover_style: StyleRefinement, pub group_hover_style: Option, drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>, @@ -867,7 +880,9 @@ impl Default for StatelessInteractivity { mouse_up_listeners: SmallVec::new(), mouse_move_listeners: SmallVec::new(), scroll_wheel_listeners: SmallVec::new(), - key_listeners: SmallVec::new(), + key_down_listeners: SmallVec::new(), + key_up_listeners: SmallVec::new(), + action_listeners: SmallVec::new(), hover_style: StyleRefinement::default(), group_hover_style: None, drag_over_styles: SmallVec::new(), @@ -1202,16 +1217,14 @@ pub(crate) type HoverListener = Box) pub(crate) type TooltipBuilder = Arc) -> AnyView + 'static>; -pub type KeyListener = Box< - dyn Fn( - &mut V, - &dyn Any, - &[&KeyContext], - DispatchPhase, - &mut ViewContext, - ) -> Option> - + 'static, ->; +pub(crate) type KeyDownListener = + Box) + 'static>; + +pub(crate) type KeyUpListener = + Box) + 'static>; + +pub type ActionListener = + Box) + 'static>; #[cfg(test)] mod test { diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index 9f76df82c3..40d6c66973 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -69,6 +69,7 @@ impl KeyDispatcher { }); self.node_stack.push(node_id); if !context.is_empty() { + self.active_node().context = context.clone(); self.context_stack.push(context); if let Some((context_stack, matcher)) = old_dispatcher .keystroke_matchers @@ -153,6 +154,7 @@ impl KeyDispatcher { // Capture phase self.context_stack.clear(); cx.propagate_event = true; + for node_id in &dispatch_path { let node = &self.nodes[node_id.0]; if !node.context.is_empty() { @@ -193,18 +195,16 @@ impl KeyDispatcher { ); } - if let Some(keystroke_matcher) = self + let keystroke_matcher = self .keystroke_matchers .get_mut(self.context_stack.as_slice()) + .unwrap(); + if let KeyMatch::Some(action) = keystroke_matcher + .match_keystroke(&key_down_event.keystroke, self.context_stack.as_slice()) { - if let KeyMatch::Some(action) = keystroke_matcher.match_keystroke( - &key_down_event.keystroke, - self.context_stack.as_slice(), - ) { - self.dispatch_action_on_node(*node_id, action, cx); - if !cx.propagate_event { - return; - } + self.dispatch_action_on_node(*node_id, action, cx); + if !cx.propagate_event { + return; } } } @@ -236,10 +236,17 @@ impl KeyDispatcher { // Capture phase for node_id in &dispatch_path { let node = &self.nodes[node_id.0]; - for ActionListener { listener, .. } in &node.action_listeners { - listener(&action, DispatchPhase::Capture, cx); - if !cx.propagate_event { - return; + for ActionListener { + action_type, + listener, + } in &node.action_listeners + { + let any_action = action.as_any(); + if *action_type == any_action.type_id() { + listener(any_action, DispatchPhase::Capture, cx); + if !cx.propagate_event { + return; + } } } } @@ -247,11 +254,18 @@ impl KeyDispatcher { // Bubble phase for node_id in dispatch_path.iter().rev() { let node = &self.nodes[node_id.0]; - for ActionListener { listener, .. } in &node.action_listeners { - cx.propagate_event = false; // Actions stop propagation by default during the bubble phase - listener(&action, DispatchPhase::Capture, cx); - if !cx.propagate_event { - return; + for ActionListener { + action_type, + listener, + } in &node.action_listeners + { + let any_action = action.as_any(); + if *action_type == any_action.type_id() { + cx.propagate_event = false; // Actions stop propagation by default during the bubble phase + listener(any_action, DispatchPhase::Bubble, cx); + if !cx.propagate_event { + return; + } } } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 3da2664f79..82d5982475 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -713,6 +713,42 @@ impl<'a> WindowContext<'a> { )) } + /// Register a key event listener on the window for the current frame. The type of event + /// is determined by the first parameter of the given listener. When the next frame is rendered + /// the listener will be cleared. + /// + /// This is a fairly low-level method, so prefer using event handlers on elements unless you have + /// a specific need to register a global listener. + pub fn on_key_event( + &mut self, + handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, + ) { + let key_dispatcher = self.window.current_frame.key_dispatcher.as_mut().unwrap(); + key_dispatcher.on_key_event(Box::new(move |event, phase, cx| { + if let Some(event) = event.downcast_ref::() { + handler(event, phase, cx) + } + })); + } + + /// Register an action listener on the window for the current frame. The type of action + /// is determined by the first parameter of the given listener. When the next frame is rendered + /// the listener will be cleared. + /// + /// This is a fairly low-level method, so prefer using action handlers on elements unless you have + /// a specific need to register a global listener. + pub fn on_action( + &mut self, + action_type: TypeId, + handler: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static, + ) { + let key_dispatcher = self.window.current_frame.key_dispatcher.as_mut().unwrap(); + key_dispatcher.on_action( + action_type, + Box::new(move |action, phase, cx| handler(action, phase, cx)), + ); + } + /// The position of the mouse relative to the window. pub fn mouse_position(&self) -> Point { self.window.mouse_position @@ -1955,6 +1991,32 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + pub fn on_key_event( + &mut self, + handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, + ) { + let handle = self.view(); + self.window_cx.on_key_event(move |event, phase, cx| { + handle.update(cx, |view, cx| { + handler(view, event, phase, cx); + }) + }); + } + + pub fn on_action( + &mut self, + action_type: TypeId, + handler: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext) + 'static, + ) { + let handle = self.view(); + self.window_cx + .on_action(action_type, move |action, phase, cx| { + handle.update(cx, |view, cx| { + handler(view, action, phase, cx); + }) + }); + } + /// Set an input handler, such as [ElementInputHandler], which interfaces with the /// platform to receive textual input with proper integration with concerns such /// as IME interactions. diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index 368fb20fbf..142e71cde4 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -39,10 +39,10 @@ impl Render for FocusStory { .focusable() .context("parent") .on_action(|_, action: &ActionA, cx| { - println!("Action A dispatched on parent during"); + println!("Action A dispatched on parent"); }) .on_action(|_, action: &ActionB, cx| { - println!("Action B dispatched on parent during"); + println!("Action B dispatched on parent"); }) .on_focus(|_, _, _| println!("Parent focused")) .on_blur(|_, _, _| println!("Parent blurred")) @@ -79,7 +79,7 @@ impl Render for FocusStory { .track_focus(&child_2) .context("child-2") .on_action(|_, action: &ActionC, cx| { - println!("Action C dispatched on child 2 during"); + println!("Action C dispatched on child 2"); }) .w_full() .h_6() From 26d26fadb340aafbf497402d34b0ab79a949a65a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 13 Nov 2023 14:35:49 +0100 Subject: [PATCH 05/15] Fix focus story --- crates/storybook2/src/stories/focus.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index 142e71cde4..bba798d9fe 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -1,12 +1,16 @@ use gpui::{ - actions, div, Div, Focusable, FocusableKeyDispatch, 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 { @@ -16,7 +20,10 @@ 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(), + }) } } @@ -31,8 +38,6 @@ 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") @@ -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,7 +81,7 @@ 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"); From 827b16bf5c7ec811aaf719b4870fc1ddd91e3891 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 13 Nov 2023 14:42:16 +0100 Subject: [PATCH 06/15] Capture node in dispatch tree even if it's not focusable --- crates/gpui2/src/key_dispatch.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index 40d6c66973..6dac90fc59 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -305,7 +305,7 @@ pub trait KeyDispatch: 'static { cx: &mut ViewContext, f: impl FnOnce(Option, &mut ViewContext) -> R, ) -> R { - if let Some(focusable) = self.as_focusable_mut() { + 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())) @@ -316,11 +316,12 @@ pub trait KeyDispatch: 'static { listener(view, &focus_handle, event, cx) }); } - - cx.with_key_dispatch(self.key_context().clone(), Some(focus_handle), f) + Some(focus_handle) } else { - f(None, cx) - } + None + }; + + cx.with_key_dispatch(self.key_context().clone(), focus_handle, f) } fn refine_style(&self, style: &mut Style, cx: &WindowContext) { From d0b5c654aae5ea4cf45660aba9bfae936bab2bba Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 13 Nov 2023 14:48:08 +0100 Subject: [PATCH 07/15] Clear pending keystrokes when finding action --- crates/gpui2/src/key_dispatch.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index 6dac90fc59..e44dc51c05 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -202,6 +202,11 @@ impl KeyDispatcher { if let KeyMatch::Some(action) = keystroke_matcher .match_keystroke(&key_down_event.keystroke, self.context_stack.as_slice()) { + // Clear all pending keystrokes when an action has been found. + for keystroke_matcher in self.keystroke_matchers.values_mut() { + keystroke_matcher.clear_pending(); + } + self.dispatch_action_on_node(*node_id, action, cx); if !cx.propagate_event { return; From c8fb8e2859eb5b9476c6489a2a0299f269f75a1f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 13 Nov 2023 15:20:43 +0100 Subject: [PATCH 08/15] :lipstick: --- crates/editor2/src/element.rs | 252 +++++++++++++++++----------------- 1 file changed, 126 insertions(+), 126 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index b273c5914a..1b0f3c473c 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -2460,150 +2460,150 @@ impl Element for EditorElement { dispatch_context, Some(editor.focus_handle.clone()), |_, cx| { - handle_action(cx, Editor::move_left); - handle_action(cx, Editor::move_right); - handle_action(cx, Editor::move_down); - handle_action(cx, Editor::move_up); + 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!() - handle_action(cx, Editor::cancel); - handle_action(cx, Editor::newline); - handle_action(cx, Editor::newline_above); - handle_action(cx, Editor::newline_below); - handle_action(cx, Editor::backspace); - handle_action(cx, Editor::delete); - handle_action(cx, Editor::tab); - handle_action(cx, Editor::tab_prev); - handle_action(cx, Editor::indent); - handle_action(cx, Editor::outdent); - handle_action(cx, Editor::delete_line); - handle_action(cx, Editor::join_lines); - handle_action(cx, Editor::sort_lines_case_sensitive); - handle_action(cx, Editor::sort_lines_case_insensitive); - handle_action(cx, Editor::reverse_lines); - handle_action(cx, Editor::shuffle_lines); - handle_action(cx, Editor::convert_to_upper_case); - handle_action(cx, Editor::convert_to_lower_case); - handle_action(cx, Editor::convert_to_title_case); - handle_action(cx, Editor::convert_to_snake_case); - handle_action(cx, Editor::convert_to_kebab_case); - handle_action(cx, Editor::convert_to_upper_camel_case); - handle_action(cx, Editor::convert_to_lower_camel_case); - handle_action(cx, Editor::delete_to_previous_word_start); - handle_action(cx, Editor::delete_to_previous_subword_start); - handle_action(cx, Editor::delete_to_next_word_end); - handle_action(cx, Editor::delete_to_next_subword_end); - handle_action(cx, Editor::delete_to_beginning_of_line); - handle_action(cx, Editor::delete_to_end_of_line); - handle_action(cx, Editor::cut_to_end_of_line); - handle_action(cx, Editor::duplicate_line); - handle_action(cx, Editor::move_line_up); - handle_action(cx, Editor::move_line_down); - handle_action(cx, Editor::transpose); - handle_action(cx, Editor::cut); - handle_action(cx, Editor::copy); - handle_action(cx, Editor::paste); - handle_action(cx, Editor::undo); - handle_action(cx, Editor::redo); - handle_action(cx, Editor::move_page_up); - handle_action(cx, Editor::move_page_down); - handle_action(cx, Editor::next_screen); - handle_action(cx, Editor::scroll_cursor_top); - handle_action(cx, Editor::scroll_cursor_center); - handle_action(cx, Editor::scroll_cursor_bottom); - handle_action(cx, |editor, _: &LineDown, cx| { + 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) }); - handle_action(cx, |editor, _: &LineUp, cx| { + register_action(cx, |editor, _: &LineUp, cx| { editor.scroll_screen(&ScrollAmount::Line(-1.), cx) }); - handle_action(cx, |editor, _: &HalfPageDown, cx| { + register_action(cx, |editor, _: &HalfPageDown, cx| { editor.scroll_screen(&ScrollAmount::Page(0.5), cx) }); - handle_action(cx, |editor, _: &HalfPageUp, cx| { + register_action(cx, |editor, _: &HalfPageUp, cx| { editor.scroll_screen(&ScrollAmount::Page(-0.5), cx) }); - handle_action(cx, |editor, _: &PageDown, cx| { + register_action(cx, |editor, _: &PageDown, cx| { editor.scroll_screen(&ScrollAmount::Page(1.), cx) }); - handle_action(cx, |editor, _: &PageUp, cx| { + register_action(cx, |editor, _: &PageUp, cx| { editor.scroll_screen(&ScrollAmount::Page(-1.), cx) }); - handle_action(cx, Editor::move_to_previous_word_start); - handle_action(cx, Editor::move_to_previous_subword_start); - handle_action(cx, Editor::move_to_next_word_end); - handle_action(cx, Editor::move_to_next_subword_end); - handle_action(cx, Editor::move_to_beginning_of_line); - handle_action(cx, Editor::move_to_end_of_line); - handle_action(cx, Editor::move_to_start_of_paragraph); - handle_action(cx, Editor::move_to_end_of_paragraph); - handle_action(cx, Editor::move_to_beginning); - handle_action(cx, Editor::move_to_end); - handle_action(cx, Editor::select_up); - handle_action(cx, Editor::select_down); - handle_action(cx, Editor::select_left); - handle_action(cx, Editor::select_right); - handle_action(cx, Editor::select_to_previous_word_start); - handle_action(cx, Editor::select_to_previous_subword_start); - handle_action(cx, Editor::select_to_next_word_end); - handle_action(cx, Editor::select_to_next_subword_end); - handle_action(cx, Editor::select_to_beginning_of_line); - handle_action(cx, Editor::select_to_end_of_line); - handle_action(cx, Editor::select_to_start_of_paragraph); - handle_action(cx, Editor::select_to_end_of_paragraph); - handle_action(cx, Editor::select_to_beginning); - handle_action(cx, Editor::select_to_end); - handle_action(cx, Editor::select_all); - handle_action(cx, |editor, action, cx| { + 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(); }); - handle_action(cx, Editor::select_line); - handle_action(cx, Editor::split_selection_into_lines); - handle_action(cx, Editor::add_selection_above); - handle_action(cx, Editor::add_selection_below); - handle_action(cx, |editor, action, cx| { + 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(); }); - handle_action(cx, |editor, action, cx| { + register_action(cx, |editor, action, cx| { editor.select_previous(action, cx).log_err(); }); - handle_action(cx, Editor::toggle_comments); - handle_action(cx, Editor::select_larger_syntax_node); - handle_action(cx, Editor::select_smaller_syntax_node); - handle_action(cx, Editor::move_to_enclosing_bracket); - handle_action(cx, Editor::undo_selection); - handle_action(cx, Editor::redo_selection); - handle_action(cx, Editor::go_to_diagnostic); - handle_action(cx, Editor::go_to_prev_diagnostic); - handle_action(cx, Editor::go_to_hunk); - handle_action(cx, Editor::go_to_prev_hunk); - handle_action(cx, Editor::go_to_definition); - handle_action(cx, Editor::go_to_definition_split); - handle_action(cx, Editor::go_to_type_definition); - handle_action(cx, Editor::go_to_type_definition_split); - handle_action(cx, Editor::fold); - handle_action(cx, Editor::fold_at); - handle_action(cx, Editor::unfold_lines); - handle_action(cx, Editor::unfold_at); - handle_action(cx, Editor::fold_selected_ranges); - handle_action(cx, Editor::show_completions); - handle_action(cx, Editor::toggle_code_actions); + 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!() - handle_action(cx, Editor::toggle_soft_wrap); - handle_action(cx, Editor::toggle_inlay_hints); - handle_action(cx, Editor::reveal_in_finder); - handle_action(cx, Editor::copy_path); - handle_action(cx, Editor::copy_relative_path); - handle_action(cx, Editor::copy_highlight_json); - handle_action(cx, |editor, action, cx| { + 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)); }); - handle_action(cx, Editor::restart_language_server); - handle_action(cx, Editor::show_character_palette); + register_action(cx, Editor::restart_language_server); + register_action(cx, Editor::show_character_palette); // on_action(cx, Editor::confirm_completion); todo!() - handle_action(cx, |editor, action, cx| { + register_action(cx, |editor, action, cx| { editor .confirm_code_action(action, cx) .map(|task| task.detach_and_log_err(cx)); @@ -2611,13 +2611,13 @@ impl Element for EditorElement { // on_action(cx, Editor::rename); todo!() // on_action(cx, Editor::confirm_rename); todo!() // on_action(cx, Editor::find_all_references); todo!() - handle_action(cx, Editor::next_copilot_suggestion); - handle_action(cx, Editor::previous_copilot_suggestion); - handle_action(cx, Editor::copilot_suggest); - handle_action(cx, Editor::context_menu_first); - handle_action(cx, Editor::context_menu_prev); - handle_action(cx, Editor::context_menu_next); - handle_action(cx, Editor::context_menu_last); + 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); }, ) }); @@ -4154,7 +4154,7 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 { // } // } -fn handle_action( +fn register_action( cx: &mut ViewContext, listener: impl Fn(&mut Editor, &T, &mut ViewContext) + 'static, ) { From 44534b926d51ff577f6a19b95aab6f90438389ac Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 13 Nov 2023 15:21:47 +0100 Subject: [PATCH 09/15] Register actions on the right div --- crates/gpui2/src/keymap/context.rs | 20 +++++++++++++++++++- crates/workspace2/src/workspace2.rs | 15 ++++++++------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/crates/gpui2/src/keymap/context.rs b/crates/gpui2/src/keymap/context.rs index b0225e73e7..99a95531a2 100644 --- a/crates/gpui2/src/keymap/context.rs +++ b/crates/gpui2/src/keymap/context.rs @@ -1,8 +1,9 @@ use crate::SharedString; use anyhow::{anyhow, Result}; use smallvec::SmallVec; +use std::fmt; -#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] +#[derive(Clone, Default, Eq, PartialEq, Hash)] pub struct KeyContext(SmallVec<[ContextEntry; 8]>); #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -99,6 +100,23 @@ impl KeyContext { } } +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), diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index e55d59303d..21c72fd385 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -39,7 +39,7 @@ use gpui::{ actions, div, point, rems, size, Action, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter, FocusHandle, GlobalPixels, KeyContext, Model, ModelContext, ParentElement, Point, Render, Size, - StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled, Subscription, Task, + StatefulInteractive, StatelessInteractive, StatelessInteractivity, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; @@ -534,8 +534,8 @@ pub struct Workspace { workspace_actions: Vec< Box< dyn Fn( - Div>, - ) -> Div>, + Div>, + ) -> Div>, >, >, zoomed: Option, @@ -3514,8 +3514,8 @@ impl Workspace { fn add_workspace_actions_listeners( &self, - mut div: Div>, - ) -> Div> { + mut div: Div>, + ) -> Div> { for action in self.workspace_actions.iter() { div = (action)(div) } @@ -3746,7 +3746,7 @@ impl Render for Workspace { let mut context = KeyContext::default(); context.add("Workspace"); - div() + self.add_workspace_actions_listeners(div()) .context(context) .relative() .size_full() @@ -3761,7 +3761,8 @@ impl Render for Workspace { .child(self.render_titlebar(cx)) .child( // todo! should this be a component a view? - self.add_workspace_actions_listeners(div().id("workspace")) + div() + .id("workspace") .relative() .flex_1() .w_full() From 45fef27aa1807f54f9a0602b0e8d47abffb3ab4c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 13 Nov 2023 15:31:35 +0100 Subject: [PATCH 10/15] Clear all the state when clearing KeyDispatcher --- crates/gpui2/src/key_dispatch.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index e44dc51c05..b0f4a5d8d2 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -58,6 +58,9 @@ impl KeyDispatcher { 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) { From a6c95ad331899bc0350f19923090982203e97998 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 13 Nov 2023 18:29:18 +0100 Subject: [PATCH 11/15] Fix panic when querying available actions --- crates/editor2/src/element.rs | 12 +- crates/gpui2/src/key_dispatch.rs | 189 +++++---------------- crates/gpui2/src/window.rs | 281 ++++++++++++++++++++++--------- 3 files changed, 250 insertions(+), 232 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 1b0f3c473c..f8386ee271 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -15,12 +15,12 @@ use crate::{ use anyhow::Result; use collections::{BTreeMap, HashMap}; use gpui::{ - black, hsla, point, px, relative, size, transparent_black, Action, ActionListener, AnyElement, - AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, - Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, - InputHandler, KeyContext, KeyDownEvent, KeyMatch, Line, LineLayout, Modifiers, MouseButton, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size, - Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout, + black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, + BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, + ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler, + KeyContext, KeyDownEvent, 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; diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index b0f4a5d8d2..bc8e1f8f85 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -1,6 +1,6 @@ use crate::{ build_action_from_type, Action, Bounds, DispatchPhase, Element, FocusEvent, FocusHandle, - FocusId, KeyContext, KeyDownEvent, KeyMatch, Keymap, KeystrokeMatcher, MouseDownEvent, Pixels, + FocusId, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent, Pixels, Style, StyleRefinement, ViewContext, WindowContext, }; use collections::HashMap; @@ -9,11 +9,11 @@ use refineable::Refineable; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, + rc::Rc, sync::Arc, }; use util::ResultExt; -type KeyListener = Box; pub type FocusListeners = SmallVec<[FocusListener; 2]>; pub type FocusListener = Box) + 'static>; @@ -21,7 +21,7 @@ pub type FocusListener = #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub struct DispatchNodeId(usize); -pub struct KeyDispatcher { +pub(crate) struct DispatchTree { node_stack: Vec, context_stack: Vec, nodes: Vec, @@ -31,19 +31,22 @@ pub struct KeyDispatcher { } #[derive(Default)] -pub struct DispatchNode { - key_listeners: SmallVec<[KeyListener; 2]>, - action_listeners: SmallVec<[ActionListener; 16]>, - context: KeyContext, +pub(crate) struct DispatchNode { + pub key_listeners: SmallVec<[KeyListener; 2]>, + pub action_listeners: SmallVec<[ActionListener; 16]>, + pub context: KeyContext, parent: Option, } -struct ActionListener { - action_type: TypeId, - listener: Box, +type KeyListener = Rc; + +#[derive(Clone)] +pub(crate) struct ActionListener { + pub(crate) action_type: TypeId, + pub(crate) listener: Rc, } -impl KeyDispatcher { +impl DispatchTree { pub fn new(keymap: Arc>) -> Self { Self { node_stack: Vec::new(), @@ -97,7 +100,7 @@ impl KeyDispatcher { pub fn on_action( &mut self, action_type: TypeId, - listener: Box, + listener: Rc, ) { self.active_node().action_listeners.push(ActionListener { action_type, @@ -140,143 +143,40 @@ impl KeyDispatcher { actions } - pub fn dispatch_key(&mut self, target: FocusId, event: &dyn Any, cx: &mut WindowContext) { - if let Some(target_node_id) = self.focusable_node_ids.get(&target).copied() { - self.dispatch_key_on_node(target_node_id, event, cx); - } - } - - fn dispatch_key_on_node( + pub fn dispatch_key( &mut self, - node_id: DispatchNodeId, - event: &dyn Any, - cx: &mut WindowContext, - ) { - let dispatch_path = self.dispatch_path(node_id); - - // Capture phase - self.context_stack.clear(); - cx.propagate_event = true; - - for node_id in &dispatch_path { - let node = &self.nodes[node_id.0]; - if !node.context.is_empty() { - self.context_stack.push(node.context.clone()); - } - - for key_listener in &node.key_listeners { - key_listener(event, DispatchPhase::Capture, cx); - if !cx.propagate_event { - return; - } - } + keystroke: &Keystroke, + context: &[KeyContext], + ) -> Option> { + if !self + .keystroke_matchers + .contains_key(self.context_stack.as_slice()) + { + let keystroke_contexts = self.context_stack.iter().cloned().collect(); + self.keystroke_matchers.insert( + keystroke_contexts, + KeystrokeMatcher::new(self.keymap.clone()), + ); } - // Bubble phase - for node_id in dispatch_path.iter().rev() { - let node = &self.nodes[node_id.0]; - - // Handle low level key events - for key_listener in &node.key_listeners { - key_listener(event, DispatchPhase::Bubble, cx); - if !cx.propagate_event { - return; - } + let keystroke_matcher = self + .keystroke_matchers + .get_mut(self.context_stack.as_slice()) + .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(); } - // Match keystrokes - if !node.context.is_empty() { - if let Some(key_down_event) = event.downcast_ref::() { - if !self - .keystroke_matchers - .contains_key(self.context_stack.as_slice()) - { - let keystroke_contexts = self.context_stack.iter().cloned().collect(); - self.keystroke_matchers.insert( - keystroke_contexts, - KeystrokeMatcher::new(self.keymap.clone()), - ); - } - - let keystroke_matcher = self - .keystroke_matchers - .get_mut(self.context_stack.as_slice()) - .unwrap(); - if let KeyMatch::Some(action) = keystroke_matcher - .match_keystroke(&key_down_event.keystroke, self.context_stack.as_slice()) - { - // Clear all pending keystrokes when an action has been found. - for keystroke_matcher in self.keystroke_matchers.values_mut() { - keystroke_matcher.clear_pending(); - } - - self.dispatch_action_on_node(*node_id, action, cx); - if !cx.propagate_event { - return; - } - } - } - - self.context_stack.pop(); - } + Some(action) + } else { + None } } - pub fn dispatch_action( - &self, - target: FocusId, - action: Box, - cx: &mut WindowContext, - ) { - if let Some(target_node_id) = self.focusable_node_ids.get(&target).copied() { - self.dispatch_action_on_node(target_node_id, action, cx); - } - } - - fn dispatch_action_on_node( - &self, - node_id: DispatchNodeId, - action: Box, - cx: &mut WindowContext, - ) { - let dispatch_path = self.dispatch_path(node_id); - - // Capture phase - for node_id in &dispatch_path { - let node = &self.nodes[node_id.0]; - for ActionListener { - action_type, - listener, - } in &node.action_listeners - { - let any_action = action.as_any(); - if *action_type == any_action.type_id() { - listener(any_action, DispatchPhase::Capture, cx); - if !cx.propagate_event { - return; - } - } - } - } - - // Bubble phase - for node_id in dispatch_path.iter().rev() { - let node = &self.nodes[node_id.0]; - for ActionListener { - action_type, - listener, - } in &node.action_listeners - { - let any_action = action.as_any(); - if *action_type == any_action.type_id() { - cx.propagate_event = false; // Actions stop propagation by default during the bubble phase - listener(any_action, DispatchPhase::Bubble, cx); - if !cx.propagate_event { - return; - } - } - } - } + pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode { + &self.nodes[node_id.0] } fn active_node(&mut self) -> &mut DispatchNode { @@ -288,8 +188,7 @@ impl KeyDispatcher { *self.node_stack.last().unwrap() } - /// Returns the DispatchNodeIds from the root of the tree to the given target node id. - fn dispatch_path(&self, target: DispatchNodeId) -> SmallVec<[DispatchNodeId; 32]> { + 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 { @@ -299,6 +198,10 @@ impl KeyDispatcher { dispatch_path.reverse(); // Reverse the path so it goes from the root to the focused node. dispatch_path } + + pub fn focusable_node_id(&self, target: FocusId) -> Option { + self.focusable_node_ids.get(&target).copied() + } } pub trait KeyDispatch: 'static { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 82d5982475..f574d7eb5f 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1,14 +1,15 @@ use crate::{ - px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, - Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DisplayId, Edges, Effect, - Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, - Hsla, ImageData, InputEvent, IsZero, KeyContext, KeyDispatcher, LayoutId, Model, ModelContext, - Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, - Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, - PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, - RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, - Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, - WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + key_dispatch::ActionListener, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, + AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, + 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, + SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, + TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, + WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -89,9 +90,7 @@ impl FocusId { pub(crate) fn contains(&self, other: Self, cx: &WindowContext) -> bool { cx.window .current_frame - .key_dispatcher - .as_ref() - .unwrap() + .dispatch_tree .focus_contains(*self, other) } } @@ -213,7 +212,7 @@ pub struct Window { pub(crate) struct Frame { element_states: HashMap, mouse_listeners: HashMap>, - pub(crate) key_dispatcher: Option, + pub(crate) dispatch_tree: DispatchTree, pub(crate) focus_listeners: Vec, pub(crate) scene_builder: SceneBuilder, z_index_stack: StackingOrder, @@ -222,11 +221,11 @@ pub(crate) struct Frame { } impl Frame { - pub fn new(key_dispatcher: KeyDispatcher) -> Self { + pub fn new(dispatch_tree: DispatchTree) -> Self { Frame { element_states: HashMap::default(), mouse_listeners: HashMap::default(), - key_dispatcher: Some(key_dispatcher), + dispatch_tree, focus_listeners: Vec::new(), scene_builder: SceneBuilder::default(), z_index_stack: StackingOrder::default(), @@ -302,8 +301,8 @@ impl Window { layout_engine: TaffyLayoutEngine::new(), root_view: None, element_id_stack: GlobalElementId::default(), - previous_frame: Frame::new(KeyDispatcher::new(cx.keymap.clone())), - current_frame: Frame::new(KeyDispatcher::new(cx.keymap.clone())), + 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, @@ -423,9 +422,14 @@ impl<'a> WindowContext<'a> { pub fn dispatch_action(&mut self, action: Box) { if let Some(focus_handle) = self.focused() { self.defer(move |cx| { - let dispatcher = cx.window.current_frame.key_dispatcher.take().unwrap(); - dispatcher.dispatch_action(focus_handle.id, action, cx); - cx.window.current_frame.key_dispatcher = Some(dispatcher); + 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); + } }) } } @@ -723,12 +727,14 @@ impl<'a> WindowContext<'a> { &mut self, handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { - let key_dispatcher = self.window.current_frame.key_dispatcher.as_mut().unwrap(); - key_dispatcher.on_key_event(Box::new(move |event, phase, cx| { - if let Some(event) = event.downcast_ref::() { - handler(event, phase, cx) - } - })); + self.window + .current_frame + .dispatch_tree + .on_key_event(Rc::new(move |event, phase, cx| { + if let Some(event) = event.downcast_ref::() { + handler(event, phase, cx) + } + })); } /// Register an action listener on the window for the current frame. The type of action @@ -742,10 +748,9 @@ impl<'a> WindowContext<'a> { action_type: TypeId, handler: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static, ) { - let key_dispatcher = self.window.current_frame.key_dispatcher.as_mut().unwrap(); - key_dispatcher.on_action( + self.window.current_frame.dispatch_tree.on_action( action_type, - Box::new(move |action, phase, cx| handler(action, phase, cx)), + Rc::new(move |action, phase, cx| handler(action, phase, cx)), ); } @@ -1110,7 +1115,7 @@ impl<'a> WindowContext<'a> { frame.element_states.clear(); frame.mouse_listeners.values_mut().for_each(Vec::clear); frame.focus_listeners.clear(); - frame.key_dispatcher.as_mut().map(KeyDispatcher::clear); + frame.dispatch_tree.clear(); } /// Dispatch a mouse or keyboard event on the window. @@ -1172,63 +1177,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); + !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(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(any_mouse_event, DispatchPhase::Bubble, self); - if !self.app.propagate_event { - break; - } - } - } + if self.app.propagate_event && event.downcast_ref::().is_some() { + self.active_drag = None; + } - if self.app.propagate_event - && any_mouse_event.downcast_ref::().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); + .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; + } + } } - } else if let Some(any_key_event) = event.keyboard_event() { - if let Some(focus_id) = self.window.focus { - let mut dispatcher = self.window.current_frame.key_dispatcher.take().unwrap(); - dispatcher.dispatch_key(focus_id, any_key_event, self); - self.window.current_frame.key_dispatcher = Some(dispatcher); + + // Bubble phase + for node_id in dispatch_path.iter().rev() { + // Handle low level key events + let node = self.window.current_frame.dispatch_tree.node(*node_id); + for key_listener in node.key_listeners.clone() { + key_listener(event, DispatchPhase::Bubble, self); + if !self.propagate_event { + return; + } + } + + // Match keystrokes + let node = self.window.current_frame.dispatch_tree.node(*node_id); + if !node.context.is_empty() { + if let Some(key_down_event) = event.downcast_ref::() { + if let Some(action) = self + .window + .current_frame + .dispatch_tree + .dispatch_key(&key_down_event.keystroke, &context_stack) + { + self.dispatch_action_on_node(*node_id, action); + if !self.propagate_event { + return; + } + } + } + + context_stack.pop(); + } + } + } + } + + fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box) { + let dispatch_path = self + .window + .current_frame + .dispatch_tree + .dispatch_path(node_id); + + // Capture phase + for node_id in &dispatch_path { + let node = self.window.current_frame.dispatch_tree.node(*node_id); + for ActionListener { + 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; + } + } } } - !self.app.propagate_event + // Bubble phase + for node_id in dispatch_path.iter().rev() { + let node = self.window.current_frame.dispatch_tree.node(*node_id); + for ActionListener { + 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 @@ -1261,9 +1375,7 @@ impl<'a> WindowContext<'a> { if let Some(focus_id) = self.window.focus { self.window .current_frame - .key_dispatcher - .as_ref() - .unwrap() + .dispatch_tree .available_actions(focus_id) } else { Vec::new() @@ -1926,17 +2038,20 @@ impl<'a, V: 'static> ViewContext<'a, V> { f: impl FnOnce(Option, &mut Self) -> R, ) -> R { let window = &mut self.window; - let old_dispatcher = window.previous_frame.key_dispatcher.as_mut().unwrap(); - let current_dispatcher = window.current_frame.key_dispatcher.as_mut().unwrap(); - current_dispatcher.push_node(context, old_dispatcher); + window + .current_frame + .dispatch_tree + .push_node(context, &mut window.previous_frame.dispatch_tree); if let Some(focus_handle) = focus_handle.as_ref() { - current_dispatcher.make_focusable(focus_handle.id); + window + .current_frame + .dispatch_tree + .make_focusable(focus_handle.id); } let result = f(focus_handle, self); - let current_dispatcher = self.window.current_frame.key_dispatcher.as_mut().unwrap(); - current_dispatcher.pop_node(); + self.window.current_frame.dispatch_tree.pop_node(); result } From 348760556aded99003930b03da362e69b9f76347 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 13 Nov 2023 18:33:08 +0100 Subject: [PATCH 12/15] :lipstick: --- crates/gpui2/src/key_dispatch.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index bc8e1f8f85..e517b8d34b 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -175,19 +175,6 @@ impl DispatchTree { } } - 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] - } - - fn active_node_id(&self) -> DispatchNodeId { - *self.node_stack.last().unwrap() - } - 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); @@ -199,9 +186,22 @@ impl DispatchTree { 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 { self.focusable_node_ids.get(&target).copied() } + + fn active_node_id(&self) -> DispatchNodeId { + *self.node_stack.last().unwrap() + } } pub trait KeyDispatch: 'static { From 2625051f75adf52c046a1ffd404ef3d2c669c373 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 13 Nov 2023 11:32:05 -0700 Subject: [PATCH 13/15] Better fix for multiple focuses in one frame --- crates/gpui2/src/app.rs | 17 +++++++++++------ crates/gpui2/src/window.rs | 4 ---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 356cf1b76b..61c6195d90 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -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 }; diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index f574d7eb5f..11878c15fa 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -389,10 +389,6 @@ impl<'a> WindowContext<'a> { pub fn focus(&mut self, handle: &FocusHandle) { let focus_id = handle.id; - if self.window.focus == Some(focus_id) { - return; - } - if self.window.last_blur.is_none() { self.window.last_blur = Some(self.window.focus); } From 7e7b06553540594feee9e654571f89a079ea37fe Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 13 Nov 2023 12:48:36 -0700 Subject: [PATCH 14/15] Fix on_action on focusable We were accidentally dropping the key context --- crates/go_to_line2/src/go_to_line.rs | 7 ++-- crates/gpui2/src/elements/div.rs | 6 +-- crates/gpui2/src/interactive.rs | 5 ++- crates/gpui2/src/key_dispatch.rs | 55 ++++++++++------------------ crates/gpui2/src/window.rs | 6 +-- crates/picker2/src/picker2.rs | 9 ++--- 6 files changed, 34 insertions(+), 54 deletions(-) diff --git a/crates/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index 9ec770e05c..50592901b5 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -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>; + type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { modal(cx) - .id("go to line") + .context("GoToLine") .on_action(Self::cancel) .on_action(Self::confirm) .w_96() diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 7bfd4b244a..95c44038ed 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -128,7 +128,7 @@ impl Div, NonFocusableKeyDispatch> { pub fn focusable(self) -> Div, FocusableKeyDispatch> { Div { interactivity: self.interactivity, - key_dispatch: FocusableKeyDispatch::new(), + key_dispatch: FocusableKeyDispatch::new(self.key_dispatch), children: self.children, group: self.group, base_style: self.base_style, @@ -141,7 +141,7 @@ impl Div, NonFocusableKeyDispatch> { ) -> Div, FocusableKeyDispatch> { Div { interactivity: self.interactivity, - key_dispatch: FocusableKeyDispatch::tracked(handle), + key_dispatch: FocusableKeyDispatch::tracked(self.key_dispatch, handle), children: self.children, group: self.group, base_style: self.base_style, @@ -172,7 +172,7 @@ impl Div, NonFocusableKeyDispatch> { ) -> Div, FocusableKeyDispatch> { Div { interactivity: self.interactivity.into_stateful(handle), - key_dispatch: handle.clone().into(), + key_dispatch: FocusableKeyDispatch::tracked(self.key_dispatch, handle), children: self.children, group: self.group, base_style: self.base_style, diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 4a7633f8dc..aacaeac01f 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1247,9 +1247,10 @@ mod test { fn render(&mut self, _: &mut gpui::ViewContext) -> 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), ) } } diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index e517b8d34b..8ace4188ae 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -33,7 +33,7 @@ pub(crate) struct DispatchTree { #[derive(Default)] pub(crate) struct DispatchNode { pub key_listeners: SmallVec<[KeyListener; 2]>, - pub action_listeners: SmallVec<[ActionListener; 16]>, + pub action_listeners: SmallVec<[DispatchActionListener; 16]>, pub context: KeyContext, parent: Option, } @@ -41,7 +41,7 @@ pub(crate) struct DispatchNode { type KeyListener = Rc; #[derive(Clone)] -pub(crate) struct ActionListener { +pub(crate) struct DispatchActionListener { pub(crate) action_type: TypeId, pub(crate) listener: Rc, } @@ -102,10 +102,12 @@ impl DispatchTree { action_type: TypeId, listener: Rc, ) { - self.active_node().action_listeners.push(ActionListener { - action_type, - listener, - }); + self.active_node() + .action_listeners + .push(DispatchActionListener { + action_type, + listener, + }); } pub fn make_focusable(&mut self, focus_id: FocusId) { @@ -135,7 +137,7 @@ impl DispatchTree { 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 ActionListener { action_type, .. } in &node.action_listeners { + for DispatchActionListener { action_type, .. } in &node.action_listeners { actions.extend(build_action_from_type(action_type).log_err()); } } @@ -148,21 +150,15 @@ impl DispatchTree { keystroke: &Keystroke, context: &[KeyContext], ) -> Option> { - if !self - .keystroke_matchers - .contains_key(self.context_stack.as_slice()) - { - let keystroke_contexts = self.context_stack.iter().cloned().collect(); + 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(self.context_stack.as_slice()) - .unwrap(); + 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() { @@ -274,7 +270,7 @@ pub trait KeyDispatch: 'static { } pub struct FocusableKeyDispatch { - pub key_context: KeyContext, + pub non_focusable: NonFocusableKeyDispatch, pub focus_handle: Option, pub focus_listeners: FocusListeners, pub focus_style: StyleRefinement, @@ -283,9 +279,9 @@ pub struct FocusableKeyDispatch { } impl FocusableKeyDispatch { - pub fn new() -> Self { + pub fn new(non_focusable: NonFocusableKeyDispatch) -> Self { Self { - key_context: KeyContext::default(), + non_focusable, focus_handle: None, focus_listeners: FocusListeners::default(), focus_style: StyleRefinement::default(), @@ -294,9 +290,9 @@ impl FocusableKeyDispatch { } } - pub fn tracked(handle: &FocusHandle) -> Self { + pub fn tracked(non_focusable: NonFocusableKeyDispatch, handle: &FocusHandle) -> Self { Self { - key_context: KeyContext::default(), + non_focusable, focus_handle: Some(handle.clone()), focus_listeners: FocusListeners::default(), focus_style: StyleRefinement::default(), @@ -316,24 +312,11 @@ impl KeyDispatch for FocusableKeyDispatch { } fn key_context(&self) -> &KeyContext { - &self.key_context + &self.non_focusable.key_context } fn key_context_mut(&mut self) -> &mut KeyContext { - &mut self.key_context - } -} - -impl From for FocusableKeyDispatch { - fn from(value: FocusHandle) -> Self { - Self { - key_context: KeyContext::default(), - focus_handle: Some(value), - focus_listeners: FocusListeners::default(), - focus_style: StyleRefinement::default(), - focus_in_style: StyleRefinement::default(), - in_focus_style: StyleRefinement::default(), - } + &mut self.non_focusable.key_context } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 11878c15fa..4a7241a5c5 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1,5 +1,5 @@ use crate::{ - key_dispatch::ActionListener, 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, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, @@ -1306,7 +1306,7 @@ impl<'a> WindowContext<'a> { // Capture phase for node_id in &dispatch_path { let node = self.window.current_frame.dispatch_tree.node(*node_id); - for ActionListener { + for DispatchActionListener { action_type, listener, } in node.action_listeners.clone() @@ -1324,7 +1324,7 @@ impl<'a> WindowContext<'a> { // Bubble phase for node_id in dispatch_path.iter().rev() { let node = self.window.current_frame.dispatch_tree.node(*node_id); - for ActionListener { + for DispatchActionListener { action_type, listener, } in node.action_listeners.clone() diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 62c5308dec..9d75fcb890 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -1,8 +1,7 @@ use editor::Editor; use gpui::{ - div, uniform_list, Component, Div, FocusableKeyDispatch, 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; use theme::ActiveTheme; @@ -137,13 +136,11 @@ impl Picker { } impl Render for Picker { - type Element = Div, FocusableKeyDispatch>; + type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { div() .context("picker") - .id("picker-container") - .focusable() .size_full() .on_action(Self::select_next) .on_action(Self::select_prev) From f8bc9be2841d9f5e54f73de48fe1682ecddf6de9 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 13 Nov 2023 13:52:49 -0700 Subject: [PATCH 15/15] Fix test --- crates/gpui2/src/keymap/context.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/gpui2/src/keymap/context.rs b/crates/gpui2/src/keymap/context.rs index 99a95531a2..b9cb0384ec 100644 --- a/crates/gpui2/src/keymap/context.rs +++ b/crates/gpui2/src/keymap/context.rs @@ -312,15 +312,15 @@ mod tests { #[test] fn test_parse_context() { let mut expected = KeyContext::default(); - expected.set("foo", "bar"); expected.add("baz"); + expected.set("foo", "bar"); assert_eq!(KeyContext::parse("baz foo=bar").unwrap(), expected); - assert_eq!(KeyContext::parse("foo = bar baz").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(" foo = bar baz").unwrap(), expected); + assert_eq!(KeyContext::parse(" baz foo = bar").unwrap(), expected); } #[test]