diff --git a/crates/gpui3/src/elements/div.rs b/crates/gpui3/src/elements/div.rs index b534812b9933023154510b00c8fec9807b523abf..d4663c950de97badbaf6e368c36c7d25a8e73bf5 100644 --- a/crates/gpui3/src/elements/div.rs +++ b/crates/gpui3/src/elements/div.rs @@ -141,14 +141,14 @@ where pub fn compute_style( &self, bounds: Bounds, - state: &InteractiveElementState, + element_state: &DivState, cx: &mut ViewContext, ) -> Style { let mut computed_style = Style::default(); computed_style.refine(&self.base_style); self.focus.refine_style(&mut computed_style, cx); self.interaction - .refine_style(&mut computed_style, bounds, state, cx); + .refine_style(&mut computed_style, bounds, &element_state.interactive, cx); computed_style } } @@ -157,13 +157,23 @@ impl Div, FocusDisabled> where V: 'static + Send + Sync, { - pub fn focusable( + pub fn focusable(self) -> Div, FocusEnabled> { + Div { + interaction: self.interaction, + focus: FocusEnabled::new(), + children: self.children, + group: self.group, + base_style: self.base_style, + } + } + + pub fn track_focus( self, handle: &FocusHandle, ) -> Div, FocusEnabled> { Div { interaction: self.interaction, - focus: handle.clone().into(), + focus: FocusEnabled::tracked(handle), children: self.children, group: self.group, base_style: self.base_style, @@ -175,7 +185,7 @@ impl Div, FocusDisabled> where V: 'static + Send + Sync, { - pub fn focusable( + pub fn track_focus( self, handle: &FocusHandle, ) -> Div, FocusEnabled> { @@ -198,10 +208,6 @@ where &mut self.focus.focus_listeners } - fn handle(&self) -> &FocusHandle { - &self.focus.focus_handle - } - fn set_focus_style(&mut self, style: StyleRefinement) { self.focus.focus_style = style; } @@ -215,6 +221,12 @@ where } } +#[derive(Default)] +pub struct DivState { + interactive: InteractiveElementState, + focus_handle: Option, +} + impl Element for Div where I: ElementInteraction, @@ -222,7 +234,7 @@ where V: 'static + Send + Sync, { type ViewState = V; - type ElementState = InteractiveElementState; + type ElementState = DivState; fn id(&self) -> Option { self.interaction @@ -236,14 +248,17 @@ where element_state: Option, cx: &mut ViewContext, ) -> Self::ElementState { - self.focus.initialize(cx, |focus_handle, cx| { - self.interaction - .initialize(element_state, focus_handle, cx, |cx| { + let mut element_state = element_state.unwrap_or_default(); + self.focus + .initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| { + element_state.focus_handle = focus_handle; + self.interaction.initialize(cx, |cx| { for child in &mut self.children { child.initialize(view_state, cx); } }) - }) + }); + element_state } fn layout( @@ -286,7 +301,8 @@ where style.paint(bounds, cx); this.focus.paint(bounds, cx); - this.interaction.paint(bounds, element_state, cx); + this.interaction + .paint(bounds, &element_state.interactive, cx); }); cx.stack(1, |cx| { diff --git a/crates/gpui3/src/elements/img.rs b/crates/gpui3/src/elements/img.rs index 273126313633905d9b30865c5c42ff0f583a4433..50e9e29fc58f2fb79ba6ca43d1be8cf752d8b0ae 100644 --- a/crates/gpui3/src/elements/img.rs +++ b/crates/gpui3/src/elements/img.rs @@ -1,9 +1,8 @@ use crate::{ - div, AnyElement, BorrowWindow, Bounds, Div, Element, ElementFocus, ElementId, - ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, - InteractiveElementState, IntoAnyElement, LayoutId, Pixels, SharedString, StatefulInteraction, - StatefulInteractive, StatelessInteraction, StatelessInteractive, StyleRefinement, Styled, - ViewContext, + div, AnyElement, BorrowWindow, Bounds, Div, DivState, Element, ElementFocus, ElementId, + ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, IntoAnyElement, + LayoutId, Pixels, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, + StatelessInteractive, StyleRefinement, Styled, ViewContext, }; use futures::FutureExt; use util::ResultExt; @@ -78,7 +77,7 @@ where F: ElementFocus, { type ViewState = V; - type ElementState = InteractiveElementState; + type ElementState = DivState; fn id(&self) -> Option { self.base.id() @@ -192,8 +191,4 @@ where fn set_in_focus_style(&mut self, style: StyleRefinement) { self.base.set_in_focus_style(style) } - - fn handle(&self) -> &crate::FocusHandle { - self.base.handle() - } } diff --git a/crates/gpui3/src/elements/svg.rs b/crates/gpui3/src/elements/svg.rs index d59c1b8f04dcdaad825437ec296c806ae6633e85..fd55296a3a6aff6ac221614685154c16273b949d 100644 --- a/crates/gpui3/src/elements/svg.rs +++ b/crates/gpui3/src/elements/svg.rs @@ -1,8 +1,8 @@ use crate::{ - div, AnyElement, Bounds, Div, Element, ElementFocus, ElementId, ElementInteraction, - FocusDisabled, FocusEnabled, FocusListeners, Focusable, InteractiveElementState, - IntoAnyElement, LayoutId, Pixels, SharedString, StatefulInteraction, StatefulInteractive, - StatelessInteraction, StatelessInteractive, StyleRefinement, Styled, ViewContext, + div, AnyElement, Bounds, Div, DivState, Element, ElementFocus, ElementId, ElementInteraction, + FocusDisabled, FocusEnabled, FocusListeners, Focusable, IntoAnyElement, LayoutId, Pixels, + SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, + StatelessInteractive, StyleRefinement, Styled, ViewContext, }; use util::ResultExt; @@ -68,7 +68,7 @@ where F: ElementFocus, { type ViewState = V; - type ElementState = InteractiveElementState; + type ElementState = DivState; fn id(&self) -> Option { self.base.id() @@ -165,8 +165,4 @@ where fn set_in_focus_style(&mut self, style: StyleRefinement) { self.base.set_in_focus_style(style) } - - fn handle(&self) -> &crate::FocusHandle { - self.base.handle() - } } diff --git a/crates/gpui3/src/focusable.rs b/crates/gpui3/src/focusable.rs index 6b6646a6d300782b3b40019666ba6e9dab88d0ff..2173d704163eb9a39f1045573d75f47f3b147772 100644 --- a/crates/gpui3/src/focusable.rs +++ b/crates/gpui3/src/focusable.rs @@ -9,14 +9,13 @@ use std::sync::Arc; pub type FocusListeners = SmallVec<[FocusListener; 2]>; pub type FocusListener = - Arc) + Send + Sync + 'static>; + Arc) + Send + Sync + '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 handle(&self) -> &FocusHandle; fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where @@ -52,10 +51,9 @@ pub trait Focusable: Element { where Self: Sized, { - let handle = self.handle().clone(); self.focus_listeners() - .push(Arc::new(move |view, event, cx| { - if event.focused.as_ref() == Some(&handle) { + .push(Arc::new(move |view, focus_handle, event, cx| { + if event.focused.as_ref() == Some(focus_handle) { listener(view, event, cx) } })); @@ -72,10 +70,9 @@ pub trait Focusable: Element { where Self: Sized, { - let handle = self.handle().clone(); self.focus_listeners() - .push(Arc::new(move |view, event, cx| { - if event.blurred.as_ref() == Some(&handle) { + .push(Arc::new(move |view, focus_handle, event, cx| { + if event.blurred.as_ref() == Some(focus_handle) { listener(view, event, cx) } })); @@ -92,17 +89,16 @@ pub trait Focusable: Element { where Self: Sized, { - let handle = self.handle().clone(); self.focus_listeners() - .push(Arc::new(move |view, event, cx| { + .push(Arc::new(move |view, focus_handle, event, cx| { let descendant_blurred = event .blurred .as_ref() - .map_or(false, |blurred| handle.contains(blurred, cx)); + .map_or(false, |blurred| focus_handle.contains(blurred, cx)); let descendant_focused = event .focused .as_ref() - .map_or(false, |focused| handle.contains(focused, cx)); + .map_or(false, |focused| focus_handle.contains(focused, cx)); if !descendant_blurred && descendant_focused { listener(view, event, cx) @@ -121,17 +117,16 @@ pub trait Focusable: Element { where Self: Sized, { - let handle = self.handle().clone(); self.focus_listeners() - .push(Arc::new(move |view, event, cx| { + .push(Arc::new(move |view, focus_handle, event, cx| { let descendant_blurred = event .blurred .as_ref() - .map_or(false, |blurred| handle.contains(blurred, cx)); + .map_or(false, |blurred| focus_handle.contains(blurred, cx)); let descendant_focused = event .focused .as_ref() - .map_or(false, |focused| handle.contains(focused, cx)); + .map_or(false, |focused| focus_handle.contains(focused, cx)); if descendant_blurred && !descendant_focused { listener(view, event, cx) } @@ -142,17 +137,25 @@ pub trait Focusable: Element { pub trait ElementFocus: 'static + Send + Sync { fn as_focusable(&self) -> Option<&FocusEnabled>; + fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled>; fn initialize( - &self, + &mut self, + focus_handle: Option, cx: &mut ViewContext, f: impl FnOnce(Option, &mut ViewContext) -> R, ) -> R { - if let Some(focusable) = self.as_focusable() { + 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.iter().cloned() { - cx.on_focus_changed(move |view, event, cx| listener(view, event, cx)); + let focus_handle = focus_handle.clone(); + cx.on_focus_changed(move |view, event, cx| { + listener(view, &focus_handle, event, cx) + }); } - let focus_handle = focusable.focus_handle.clone(); cx.with_focus(focus_handle.clone(), |cx| f(Some(focus_handle), cx)) } else { f(None, cx) @@ -161,15 +164,19 @@ pub trait ElementFocus: 'static + Send + Sync { fn refine_style(&self, style: &mut Style, cx: &WindowContext) { if let Some(focusable) = self.as_focusable() { - if focusable.focus_handle.contains_focused(cx) { + 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 focusable.focus_handle.within_focused(cx) { + if focus_handle.within_focused(cx) { style.refine(&focusable.in_focus_style); } - if focusable.focus_handle.is_focused(cx) { + if focus_handle.is_focused(cx) { style.refine(&focusable.focus_style); } } @@ -177,7 +184,10 @@ pub trait ElementFocus: 'static + Send + Sync { fn paint(&self, bounds: Bounds, cx: &mut WindowContext) { if let Some(focusable) = self.as_focusable() { - let focus_handle = focusable.focus_handle.clone(); + 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() { @@ -191,13 +201,38 @@ pub trait ElementFocus: 'static + Send + Sync { } pub struct FocusEnabled { - pub focus_handle: FocusHandle, + pub focus_handle: Option, pub focus_listeners: FocusListeners, pub focus_style: StyleRefinement, pub focus_in_style: StyleRefinement, pub in_focus_style: StyleRefinement, } +impl FocusEnabled +where + V: 'static + Send + Sync, +{ + 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 where V: 'static + Send + Sync, @@ -205,6 +240,10 @@ where fn as_focusable(&self) -> Option<&FocusEnabled> { Some(self) } + + fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled> { + Some(self) + } } impl From for FocusEnabled @@ -213,7 +252,7 @@ where { fn from(value: FocusHandle) -> Self { Self { - focus_handle: value, + focus_handle: Some(value), focus_listeners: FocusListeners::default(), focus_style: StyleRefinement::default(), focus_in_style: StyleRefinement::default(), @@ -231,4 +270,8 @@ where fn as_focusable(&self) -> Option<&FocusEnabled> { None } + + fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled> { + None + } } diff --git a/crates/gpui3/src/interactive.rs b/crates/gpui3/src/interactive.rs index 54e08e0fe39422a4ccc4de673dfb6d8b8be7887e..dd50082bae7a2e35d7708b20b561113d648d4e74 100644 --- a/crates/gpui3/src/interactive.rs +++ b/crates/gpui3/src/interactive.rs @@ -305,13 +305,11 @@ pub trait ElementInteraction: 'static + Send + Sync { fn as_stateful(&self) -> Option<&StatefulInteraction>; fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction>; - fn initialize( + fn initialize( &mut self, - element_state: Option, - focus_handle: Option, cx: &mut ViewContext, - f: impl FnOnce(&mut ViewContext), - ) -> InteractiveElementState { + 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| { stateful.key_listeners.push(( @@ -329,19 +327,15 @@ pub trait ElementInteraction: 'static + Send + Sync { None }), )); - let mut element_state = stateful.stateless.initialize(element_state, None, cx, f); - element_state.focus_handle = focus_handle - .or(element_state.focus_handle.take()) - .or_else(|| cx.focused()); + let result = stateful.stateless.initialize(cx, f); stateful.key_listeners.pop(); - element_state + result }) } else { let stateless = self.as_stateless(); cx.with_key_dispatch_context(stateless.dispatch_context.clone(), |cx| { cx.with_key_listeners(&stateless.key_listeners, f) - }); - element_state.unwrap_or_default() + }) } } @@ -613,7 +607,6 @@ impl ActiveState { #[derive(Default)] pub struct InteractiveElementState { - focus_handle: Option, active_state: Arc>, pending_click: Arc>>, } diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index 88f0c0b0b3c9cf1f1cd21329c89032e97e8e3c44..3013d948cfa8aa1a0d699a8b7645c8d8b43d3828 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -76,12 +76,12 @@ impl FocusStory { 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) + .id("parent") + .focusable() .context("parent") .on_action(|_, action: &ActionA, phase, cx| { println!("Action A dispatched on parent during {:?}", phase); @@ -105,7 +105,7 @@ impl FocusStory { .focus_in(|style| style.bg(color_3)) .child( div() - .focusable(&child_1) + .track_focus(&child_1) .context("child-1") .on_action(|_, action: &ActionB, phase, cx| { println!("Action B dispatched on child 1 during {:?}", phase); @@ -129,7 +129,7 @@ impl FocusStory { ) .child( div() - .focusable(&child_2) + .track_focus(&child_2) .context("child-2") .on_action(|_, action: &ActionC, phase, cx| { println!("Action C dispatched on child 2 during {:?}", phase);