diff --git a/crates/gpui3/src/elements/div.rs b/crates/gpui3/src/elements/div.rs index a943f4d25b9ccea9ac3d5326e6ebff8eeb4a4deb..ae81ec51ef94826a8fad9b5ee3eeb5c8aa217f63 100644 --- a/crates/gpui3/src/elements/div.rs +++ b/crates/gpui3/src/elements/div.rs @@ -324,7 +324,10 @@ where let focus_handle = focus_handle.clone(); cx.on_mouse_event(move |_, event: &MouseDownEvent, phase, cx| { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { - cx.focus(&focus_handle); + if !cx.default_prevented() { + cx.focus(&focus_handle); + cx.prevent_default(); + } } }) } diff --git a/crates/gpui3/src/focus.rs b/crates/gpui3/src/focus.rs index d6e9adc6fd5c156d412f8cf5978733beb980a5c0..12f0679655f5e7066d6b78ee47065b8edf2ad6e9 100644 --- a/crates/gpui3/src/focus.rs +++ b/crates/gpui3/src/focus.rs @@ -89,11 +89,16 @@ pub trait Focus: Interactive { self.listeners() .focus .push(Box::new(move |view, event, cx| { - if event + let descendant_blurred = event + .blurred + .as_ref() + .map_or(false, |blurred| handle.contains(blurred, cx)); + let descendant_focused = event .focused .as_ref() - .map_or(false, |focused| focused.contains(&handle, cx)) - { + .map_or(false, |focused| handle.contains(focused, cx)); + + if !descendant_blurred && descendant_focused { listener(view, event, cx) } })); @@ -114,11 +119,15 @@ pub trait Focus: Interactive { self.listeners() .focus .push(Box::new(move |view, event, cx| { - if event + let descendant_blurred = event .blurred .as_ref() - .map_or(false, |blurred| handle.contains(&blurred, cx)) - { + .map_or(false, |blurred| handle.contains(blurred, cx)); + let descendant_focused = event + .focused + .as_ref() + .map_or(false, |focused| handle.contains(focused, cx)); + if descendant_blurred && !descendant_focused { listener(view, event, cx) } })); diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index c1507df14e15c765ddb889663e59449725558ccb..e0edbac4489511bf5911877e6e75c25d5203cb92 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -160,6 +160,7 @@ pub struct Window { pub(crate) focus_listeners: Vec, pub(crate) focus_handles: Arc>>, propagate_event: bool, + default_prevented: bool, mouse_position: Point, scale_factor: f32, pub(crate) scene_builder: SceneBuilder, @@ -230,6 +231,7 @@ impl Window { focus_parents_by_child: HashMap::default(), focus_listeners: Vec::new(), propagate_event: true, + default_prevented: true, mouse_position, scale_factor, scene_builder: SceneBuilder::new(), @@ -447,6 +449,14 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.propagate_event = false; } + pub fn prevent_default(&mut self) { + self.window.default_prevented = true; + } + + pub fn default_prevented(&self) -> bool { + self.window.default_prevented + } + pub fn on_mouse_event( &mut self, handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + Send + Sync + 'static, @@ -837,6 +847,10 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.mouse_position = *position; } + // Handlers may set this to false by calling `stop_propagation` + self.window.propagate_event = true; + self.window.default_prevented = false; + if let Some(mut handlers) = self .window .mouse_listeners @@ -845,9 +859,6 @@ impl<'a, 'w> WindowContext<'a, 'w> { // Because handlers may add other handlers, we sort every time. handlers.sort_by(|(a, _), (b, _)| a.cmp(b)); - // Handlers may set this to false by calling `stop_propagation` - self.window.propagate_event = true; - // 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 &handlers { diff --git a/crates/storybook2/src/stories.rs b/crates/storybook2/src/stories.rs index f731f7e4280c07503b734a54b35866acfdeba13d..758797e71f8937fa91e8ca918524ea67a8febcb5 100644 --- a/crates/storybook2/src/stories.rs +++ b/crates/storybook2/src/stories.rs @@ -1,7 +1,9 @@ +mod focus; mod kitchen_sink; mod text; mod z_index; +pub use focus::*; pub use kitchen_sink::*; pub use text::*; pub use z_index::*; diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs new file mode 100644 index 0000000000000000000000000000000000000000..3b1d08264c084a643a6100fd0e0f1e783236010c --- /dev/null +++ b/crates/storybook2/src/stories/focus.rs @@ -0,0 +1,80 @@ +use gpui3::{div, view, Context, Focus, ParentElement, Styled, View, WindowContext}; + +use crate::themes::rose_pine; + +pub struct FocusStory { + text: View<()>, +} + +impl FocusStory { + pub fn view(cx: &mut WindowContext) -> View<()> { + let theme = rose_pine(); + + let color_1 = theme.lowest.negative.default.foreground; + let color_2 = theme.lowest.positive.default.foreground; + let color_3 = theme.lowest.warning.default.foreground; + let color_4 = theme.lowest.accent.default.foreground; + let color_5 = theme.lowest.variant.default.foreground; + let color_6 = theme.highest.negative.default.foreground; + + let parent = cx.focus_handle(); + let child_1 = cx.focus_handle(); + let child_2 = cx.focus_handle(); + view(cx.entity(|cx| ()), move |_, cx| { + div() + .focusable(&parent) + .on_focus(|_, _, _| println!("Parent focused")) + .on_blur(|_, _, _| println!("Parent blurred")) + .on_focus_in(|_, _, _| println!("Parent focus_in")) + .on_focus_out(|_, _, _| println!("Parent focus_out")) + .on_key_down(|_, event, phase, _| { + println!("Key down on parent {:?} {:?}", phase, event) + }) + .on_key_up(|_, event, phase, _| { + println!("Key up on parent {:?} {:?}", phase, event) + }) + .size_full() + .bg(color_1) + .focus(|style| style.bg(color_2)) + .focus_in(|style| style.bg(color_3)) + .child( + div() + .focusable(&child_1) + .w_full() + .h_6() + .bg(color_4) + .focus(|style| style.bg(color_5)) + .in_focus(|style| style.bg(color_6)) + .on_focus(|_, _, _| println!("Child 1 focused")) + .on_blur(|_, _, _| println!("Child 1 blurred")) + .on_focus_in(|_, _, _| println!("Child 1 focus_in")) + .on_focus_out(|_, _, _| println!("Child 1 focus_out")) + .on_key_down(|_, event, phase, _| { + println!("Key down on child 1 {:?} {:?}", phase, event) + }) + .on_key_up(|_, event, phase, _| { + println!("Key up on child 1 {:?} {:?}", phase, event) + }) + .child("Child 1"), + ) + .child( + div() + .focusable(&child_2) + .w_full() + .h_6() + .bg(color_4) + .on_focus(|_, _, _| println!("Child 2 focused")) + .on_blur(|_, _, _| println!("Child 2 blurred")) + .on_focus_in(|_, _, _| println!("Child 2 focus_in")) + .on_focus_out(|_, _, _| println!("Child 2 focus_out")) + .on_key_down(|_, event, phase, _| { + println!("Key down on child 2 {:?} {:?}", phase, event) + }) + .on_key_up(|_, event, phase, _| { + println!("Key up on child 2 {:?} {:?}", phase, event) + }) + .child("Child 2"), + ) + }) + } +} diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index 1603a46b19204d2e60d33de091767cd2296403bb..0056d697643ea7d8db17d048549f80ff7230dc86 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -15,6 +15,7 @@ pub enum ElementStory { Avatar, Button, Details, + Focus, Icon, Input, Label, @@ -35,6 +36,7 @@ impl ElementStory { ui::DetailsStory::new().into_any() }) .into_any(), + Self::Focus => FocusStory::view(cx).into_any(), Self::Icon => { view(cx.entity(|cx| ()), |_, _| ui::IconStory::new().into_any()).into_any() }