Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/app.rs          |  6 +-
crates/gpui3/src/elements/div.rs |  1 
crates/gpui3/src/events.rs       |  5 +
crates/gpui3/src/focus.rs        | 98 ++++++++++++++++++++++++++++++++++
crates/gpui3/src/gpui3.rs        |  2 
crates/gpui3/src/window.rs       | 41 ++++++++++---
6 files changed, 139 insertions(+), 14 deletions(-)

Detailed changes

crates/gpui3/src/app.rs 🔗

@@ -234,7 +234,7 @@ impl AppContext {
     fn apply_focus_changed(&mut self, window_id: WindowId, focused: Option<FocusId>) {
         self.update_window(window_id, |cx| {
             if cx.window.focus == focused {
-                let mut listeners = mem::take(&mut cx.window.focus_change_listeners);
+                let mut listeners = mem::take(&mut cx.window.focus_listeners);
                 let focused = focused.map(FocusHandle::new);
                 let blurred = cx.window.last_blur.unwrap().map(FocusHandle::new);
                 let event = FocusEvent { focused, blurred };
@@ -242,8 +242,8 @@ impl AppContext {
                     listener(&event, cx);
                 }
 
-                listeners.extend(cx.window.focus_change_listeners.drain(..));
-                cx.window.focus_change_listeners = listeners;
+                listeners.extend(cx.window.focus_listeners.drain(..));
+                cx.window.focus_listeners = listeners;
             }
         })
         .ok();

crates/gpui3/src/elements/div.rs 🔗

@@ -379,6 +379,7 @@ where
             self.focusability.focus_handle().cloned(),
             mem::take(&mut self.listeners.key_down),
             mem::take(&mut self.listeners.key_up),
+            mem::take(&mut self.listeners.focus),
             |cx| {
                 for child in &mut self.children {
                     child.initialize(view_state, cx);

crates/gpui3/src/events.rs 🔗

@@ -243,6 +243,9 @@ pub type KeyDownListener<V> =
 pub type KeyUpListener<V> =
     Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static>;
 
+pub type FocusListener<V> =
+    Box<dyn Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
+
 pub struct EventListeners<V: 'static> {
     pub mouse_down: SmallVec<[MouseDownListener<V>; 2]>,
     pub mouse_up: SmallVec<[MouseUpListener<V>; 2]>,
@@ -251,6 +254,7 @@ pub struct EventListeners<V: 'static> {
     pub scroll_wheel: SmallVec<[ScrollWheelListener<V>; 2]>,
     pub key_down: SmallVec<[KeyDownListener<V>; 2]>,
     pub key_up: SmallVec<[KeyUpListener<V>; 2]>,
+    pub focus: SmallVec<[FocusListener<V>; 2]>,
 }
 
 impl<V> Default for EventListeners<V> {
@@ -263,6 +267,7 @@ impl<V> Default for EventListeners<V> {
             scroll_wheel: SmallVec::new(),
             key_down: SmallVec::new(),
             key_up: SmallVec::new(),
+            focus: SmallVec::new(),
         }
     }
 }

crates/gpui3/src/focus.rs 🔗

@@ -0,0 +1,98 @@
+use crate::{Element, EventListeners, FocusEvent, FocusHandle, ViewContext};
+
+pub trait Focus: Element {
+    fn handle(&self) -> &FocusHandle;
+    fn listeners(&mut self) -> &mut EventListeners<Self::ViewState>;
+
+    fn on_focus(
+        mut self,
+        listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext<Self::ViewState>)
+            + Send
+            + Sync
+            + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        let handle = self.handle().clone();
+        self.listeners()
+            .focus
+            .push(Box::new(move |view, event, cx| {
+                if event.focused.as_ref() == Some(&handle) {
+                    listener(view, event, cx)
+                }
+            }));
+        self
+    }
+
+    fn on_blur(
+        mut self,
+        listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext<Self::ViewState>)
+            + Send
+            + Sync
+            + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        let handle = self.handle().clone();
+        self.listeners()
+            .focus
+            .push(Box::new(move |view, event, cx| {
+                if event.blurred.as_ref() == Some(&handle) {
+                    listener(view, event, cx)
+                }
+            }));
+        self
+    }
+
+    fn on_focus_in(
+        mut self,
+        listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext<Self::ViewState>)
+            + Send
+            + Sync
+            + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        let handle = self.handle().clone();
+        self.listeners()
+            .focus
+            .push(Box::new(move |view, event, cx| {
+                if event
+                    .focused
+                    .as_ref()
+                    .map_or(false, |focused| focused.contains(&handle, cx))
+                {
+                    listener(view, event, cx)
+                }
+            }));
+        self
+    }
+
+    fn on_focus_out(
+        mut self,
+        listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext<Self::ViewState>)
+            + Send
+            + Sync
+            + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        let handle = self.handle().clone();
+        self.listeners()
+            .focus
+            .push(Box::new(move |view, event, cx| {
+                if event
+                    .blurred
+                    .as_ref()
+                    .map_or(false, |blurred| handle.contains(&blurred, cx))
+                {
+                    listener(view, event, cx)
+                }
+            }));
+        self
+    }
+}

crates/gpui3/src/gpui3.rs 🔗

@@ -6,6 +6,7 @@ mod element;
 mod elements;
 mod events;
 mod executor;
+mod focus;
 mod geometry;
 mod hover;
 mod image_cache;
@@ -31,6 +32,7 @@ pub use element::*;
 pub use elements::*;
 pub use events::*;
 pub use executor::*;
+pub use focus::*;
 pub use geometry::*;
 pub use gpui3_macros::*;
 pub use hover::*;

crates/gpui3/src/window.rs 🔗

@@ -1,9 +1,9 @@
 use crate::{
     px, size, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace, BorrowAppContext,
     Bounds, BoxShadow, Context, Corners, DevicePixels, DisplayId, Edges, Effect, Element, EntityId,
-    EventEmitter, FocusEvent, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData,
-    InputEvent, IsZero, KeyDownEvent, KeyDownListener, KeyUpEvent, KeyUpListener, LayoutId,
-    MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path, Pixels, Platform,
+    EventEmitter, FocusEvent, FocusListener, FontId, GlobalElementId, GlyphId, Handle, Hsla,
+    ImageData, InputEvent, IsZero, KeyDownEvent, KeyDownListener, KeyUpEvent, KeyUpListener,
+    LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path, Pixels, Platform,
     PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams,
     RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
     Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle,
@@ -42,7 +42,7 @@ pub enum DispatchPhase {
 
 type AnyMouseEventListener =
     Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>;
-type AnyFocusChangeListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + Send + Sync + 'static>;
+type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + Send + Sync + 'static>;
 type AnyKeyDownListener =
     Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>;
 type AnyKeyUpListener =
@@ -51,7 +51,7 @@ type AnyKeyUpListener =
 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
 pub struct FocusId(usize);
 
-#[derive(Clone)]
+#[derive(Clone, PartialEq, Eq)]
 pub struct FocusHandle {
     pub(crate) id: FocusId,
 }
@@ -70,9 +70,14 @@ impl FocusHandle {
     }
 
     pub fn within_focused(&self, cx: &WindowContext) -> bool {
-        let mut ancestor = Some(self.id);
+        let focused = cx.focused();
+        focused.map_or(false, |focused| focused.contains(self, cx))
+    }
+
+    pub(crate) fn contains(&self, other: &Self, cx: &WindowContext) -> bool {
+        let mut ancestor = Some(other.id);
         while let Some(ancestor_id) = ancestor {
-            if cx.window.focus == Some(ancestor_id) {
+            if self.id == ancestor_id {
                 return true;
             } else {
                 ancestor = cx.window.focus_parents_by_child.get(&ancestor_id).copied();
@@ -100,7 +105,7 @@ pub struct Window {
     focus_stack: Vec<FocusStackFrame>,
     focus_parents_by_child: HashMap<FocusId, FocusId>,
     containing_focus: HashSet<FocusId>,
-    pub(crate) focus_change_listeners: Vec<AnyFocusChangeListener>,
+    pub(crate) focus_listeners: Vec<AnyFocusListener>,
     key_down_listeners: Vec<AnyKeyDownListener>,
     key_up_listeners: Vec<AnyKeyUpListener>,
     propagate_event: bool,
@@ -173,7 +178,7 @@ impl Window {
             focus_parents_by_child: HashMap::default(),
             containing_focus: HashSet::default(),
             mouse_listeners: HashMap::default(),
-            focus_change_listeners: Vec::new(),
+            focus_listeners: Vec::new(),
             key_down_listeners: Vec::new(),
             key_up_listeners: Vec::new(),
             propagate_event: true,
@@ -235,6 +240,10 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         FocusHandle { id }
     }
 
+    pub fn focused(&self) -> Option<FocusHandle> {
+        self.window.focus.map(|id| FocusHandle::new(id))
+    }
+
     pub fn focus(&mut self, handle: &FocusHandle) {
         if self.window.last_blur.is_none() {
             self.window.last_blur = Some(self.window.focus);
@@ -751,7 +760,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
 
         // Clear focus state, because we determine what is focused when the new elements
         // in the upcoming frame are initialized.
-        window.focus_change_listeners.clear();
+        window.focus_listeners.clear();
         window.key_down_listeners.clear();
         window.key_up_listeners.clear();
         window.containing_focus.clear();
@@ -1110,6 +1119,7 @@ impl<'a, 'w, V: Send + Sync + 'static> ViewContext<'a, 'w, V> {
         focus_handle: Option<FocusHandle>,
         key_down: impl IntoIterator<Item = KeyDownListener<V>>,
         key_up: impl IntoIterator<Item = KeyUpListener<V>>,
+        focus: impl IntoIterator<Item = FocusListener<V>>,
         f: impl FnOnce(&mut Self) -> R,
     ) -> R {
         let Some(focus_handle) = focus_handle else {
@@ -1118,6 +1128,16 @@ impl<'a, 'w, V: Send + Sync + 'static> ViewContext<'a, 'w, V> {
 
         let handle = self.handle();
         let window = &mut *self.window;
+
+        for listener in focus {
+            let handle = handle.clone();
+            window.focus_listeners.push(Box::new(move |event, cx| {
+                handle
+                    .update(cx, |view, cx| listener(view, event, cx))
+                    .log_err();
+            }));
+        }
+
         if let Some(parent_frame) = window.focus_stack.last() {
             window
                 .focus_parents_by_child
@@ -1150,7 +1170,6 @@ impl<'a, 'w, V: Send + Sync + 'static> ViewContext<'a, 'w, V> {
                         .log_err();
                 }));
         }
-
         window.focus_stack.push(frame);
 
         if Some(focus_handle.id) == window.focus {