Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/app.rs                   |  39 +++++++
crates/gpui3/src/events.rs                | 116 ++++++++++++++++++++----
crates/gpui3/src/interactive.rs           |  70 --------------
crates/gpui3/src/platform.rs              |  10 +-
crates/gpui3/src/platform/mac/events.rs   |   4 
crates/gpui3/src/platform/mac/platform.rs |   8 
crates/gpui3/src/platform/mac/window.rs   |  40 ++++----
crates/gpui3/src/platform/test.rs         |   2 
crates/gpui3/src/window.rs                |  58 +++++++++--
9 files changed, 210 insertions(+), 137 deletions(-)

Detailed changes

crates/gpui3/src/app.rs 🔗

@@ -8,9 +8,10 @@ pub use model_context::*;
 use refineable::Refineable;
 
 use crate::{
-    current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor, LayoutId,
-    MainThread, MainThreadOnly, Platform, SubscriberSet, SvgRenderer, Task, TextStyle,
-    TextStyleRefinement, TextSystem, View, Window, WindowContext, WindowHandle, WindowId,
+    current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor,
+    FocusEvent, FocusHandle, FocusId, LayoutId, MainThread, MainThreadOnly, Platform,
+    SubscriberSet, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
+    WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet, VecDeque};
@@ -54,6 +55,7 @@ impl App {
                 this: this.clone(),
                 text_system: Arc::new(TextSystem::new(platform.text_system())),
                 pending_updates: 0,
+                flushing_effects: false,
                 next_frame_callbacks: Default::default(),
                 platform: MainThreadOnly::new(platform, executor.clone()),
                 executor,
@@ -97,6 +99,7 @@ pub struct AppContext {
     this: Weak<Mutex<AppContext>>,
     pub(crate) platform: MainThreadOnly<dyn Platform>,
     text_system: Arc<TextSystem>,
+    flushing_effects: bool,
     pending_updates: usize,
     pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
     pub(crate) executor: Executor,
@@ -119,8 +122,10 @@ impl AppContext {
     pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
         self.pending_updates += 1;
         let result = update(self);
-        if self.pending_updates == 1 {
+        if !self.flushing_effects && self.pending_updates == 1 {
+            self.flushing_effects = true;
             self.flush_effects();
+            self.flushing_effects = false;
         }
         self.pending_updates -= 1;
         result
@@ -158,6 +163,7 @@ impl AppContext {
                 }
             }
             Effect::Emit { .. } => self.pending_effects.push_back(effect),
+            Effect::FocusChanged { .. } => self.pending_effects.push_back(effect),
         }
     }
 
@@ -168,6 +174,9 @@ impl AppContext {
                 match effect {
                     Effect::Notify { emitter } => self.apply_notify_effect(emitter),
                     Effect::Emit { emitter, event } => self.apply_emit_effect(emitter, event),
+                    Effect::FocusChanged { window_id, focused } => {
+                        self.apply_focus_changed(window_id, focused)
+                    }
                 }
             } else {
                 break;
@@ -222,6 +231,24 @@ impl AppContext {
             .retain(&emitter, |handler| handler(&event, self));
     }
 
+    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 focused = focused.map(FocusHandle::new);
+                let blurred = cx.window.last_blur.unwrap().map(FocusHandle::new);
+                let event = FocusEvent { focused, blurred };
+                for listener in &listeners {
+                    listener(&event, cx);
+                }
+
+                listeners.extend(cx.window.focus_change_listeners.drain(..));
+                cx.window.focus_change_listeners = listeners;
+            }
+        })
+        .ok();
+    }
+
     pub fn to_async(&self) -> AsyncAppContext {
         AsyncAppContext(unsafe { mem::transmute(self.this.clone()) })
     }
@@ -426,6 +453,10 @@ pub(crate) enum Effect {
         emitter: EntityId,
         event: Box<dyn Any + Send + Sync + 'static>,
     },
+    FocusChanged {
+        window_id: WindowId,
+        focused: Option<FocusId>,
+    },
 }
 
 #[cfg(test)]

crates/gpui3/src/events.rs 🔗

@@ -1,5 +1,9 @@
-use crate::{point, Keystroke, Modifiers, Pixels, Point};
-use std::{any::Any, ops::Deref};
+use smallvec::SmallVec;
+
+use crate::{
+    point, Bounds, DispatchPhase, FocusHandle, Keystroke, Modifiers, Pixels, Point, ViewContext,
+};
+use std::{any::Any, ops::Deref, sync::Arc};
 
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct KeyDownEvent {
@@ -26,7 +30,7 @@ impl Deref for ModifiersChangedEvent {
 }
 
 /// The phase of a touch motion event.
-/// Based on the winit enum of the same name,
+/// Based on the winit enum of the same name.
 #[derive(Clone, Copy, Debug)]
 pub enum TouchPhase {
     Started,
@@ -50,6 +54,12 @@ pub struct MouseUpEvent {
     pub click_count: usize,
 }
 
+#[derive(Clone, Debug, Default)]
+pub struct MouseClickEvent {
+    pub down: MouseDownEvent,
+    pub up: MouseUpEvent,
+}
+
 #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
 pub enum MouseButton {
     Left,
@@ -155,7 +165,7 @@ impl Deref for MouseExitEvent {
 }
 
 #[derive(Clone, Debug)]
-pub enum Event {
+pub enum InputEvent {
     KeyDown(KeyDownEvent),
     KeyUp(KeyUpEvent),
     ModifiersChanged(ModifiersChangedEvent),
@@ -166,30 +176,94 @@ pub enum Event {
     ScrollWheel(ScrollWheelEvent),
 }
 
-impl Event {
+impl InputEvent {
     pub fn position(&self) -> Option<Point<Pixels>> {
         match self {
-            Event::KeyDown { .. } => None,
-            Event::KeyUp { .. } => None,
-            Event::ModifiersChanged { .. } => None,
-            Event::MouseDown(event) => Some(event.position),
-            Event::MouseUp(event) => Some(event.position),
-            Event::MouseMoved(event) => Some(event.position),
-            Event::MouseExited(event) => Some(event.position),
-            Event::ScrollWheel(event) => Some(event.position),
+            InputEvent::KeyDown { .. } => None,
+            InputEvent::KeyUp { .. } => None,
+            InputEvent::ModifiersChanged { .. } => None,
+            InputEvent::MouseDown(event) => Some(event.position),
+            InputEvent::MouseUp(event) => Some(event.position),
+            InputEvent::MouseMoved(event) => Some(event.position),
+            InputEvent::MouseExited(event) => Some(event.position),
+            InputEvent::ScrollWheel(event) => Some(event.position),
         }
     }
 
     pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
         match self {
-            Event::KeyDown { .. } => None,
-            Event::KeyUp { .. } => None,
-            Event::ModifiersChanged { .. } => None,
-            Event::MouseDown(event) => Some(event),
-            Event::MouseUp(event) => Some(event),
-            Event::MouseMoved(event) => Some(event),
-            Event::MouseExited(event) => Some(event),
-            Event::ScrollWheel(event) => Some(event),
+            InputEvent::KeyDown { .. } => None,
+            InputEvent::KeyUp { .. } => None,
+            InputEvent::ModifiersChanged { .. } => None,
+            InputEvent::MouseDown(event) => Some(event),
+            InputEvent::MouseUp(event) => Some(event),
+            InputEvent::MouseMoved(event) => Some(event),
+            InputEvent::MouseExited(event) => Some(event),
+            InputEvent::ScrollWheel(event) => Some(event),
+        }
+    }
+}
+
+pub struct FocusEvent {
+    pub blurred: Option<FocusHandle>,
+    pub focused: Option<FocusHandle>,
+}
+
+pub type MouseDownListener<V> = Arc<
+    dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+        + Send
+        + Sync
+        + 'static,
+>;
+pub type MouseUpListener<V> = Arc<
+    dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+        + Send
+        + Sync
+        + 'static,
+>;
+pub type MouseClickListener<V> =
+    Arc<dyn Fn(&mut V, &MouseClickEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
+
+pub type MouseMoveListener<V> = Arc<
+    dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+        + Send
+        + Sync
+        + 'static,
+>;
+
+pub type ScrollWheelListener<V> = Arc<
+    dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+        + Send
+        + Sync
+        + 'static,
+>;
+
+pub type KeyDownListener<V> =
+    Arc<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static>;
+
+pub type KeyUpListener<V> =
+    Arc<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &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]>,
+    pub mouse_click: SmallVec<[MouseClickListener<V>; 2]>,
+    pub mouse_move: SmallVec<[MouseMoveListener<V>; 2]>,
+    pub scroll_wheel: SmallVec<[ScrollWheelListener<V>; 2]>,
+    pub key_down: SmallVec<[KeyDownListener<V>; 2]>,
+    pub key_up: SmallVec<[KeyUpListener<V>; 2]>,
+}
+
+impl<V> Default for EventListeners<V> {
+    fn default() -> Self {
+        Self {
+            mouse_down: SmallVec::new(),
+            mouse_up: SmallVec::new(),
+            mouse_click: SmallVec::new(),
+            mouse_move: SmallVec::new(),
+            scroll_wheel: SmallVec::new(),
+            key_down: SmallVec::new(),
+            key_up: SmallVec::new(),
         }
     }
 }

crates/gpui3/src/interactive.rs 🔗

@@ -1,8 +1,6 @@
-use smallvec::SmallVec;
-
 use crate::{
-    Bounds, DispatchPhase, Element, KeyDownEvent, KeyUpEvent, MouseButton, MouseDownEvent,
-    MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ViewContext,
+    DispatchPhase, Element, EventListeners, MouseButton, MouseClickEvent, MouseDownEvent,
+    MouseMoveEvent, MouseUpEvent, ScrollWheelEvent, ViewContext,
 };
 use std::sync::Arc;
 
@@ -163,67 +161,3 @@ pub trait Click: Interactive {
         self
     }
 }
-
-type MouseDownListener<V> = Arc<
-    dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
-        + Send
-        + Sync
-        + 'static,
->;
-type MouseUpListener<V> = Arc<
-    dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
-        + Send
-        + Sync
-        + 'static,
->;
-type MouseClickListener<V> =
-    Arc<dyn Fn(&mut V, &MouseClickEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
-
-type MouseMoveListener<V> = Arc<
-    dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
-        + Send
-        + Sync
-        + 'static,
->;
-
-type ScrollWheelListener<V> = Arc<
-    dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
-        + Send
-        + Sync
-        + 'static,
->;
-
-pub type KeyDownListener<V> =
-    Arc<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static>;
-
-pub type KeyUpListener<V> =
-    Arc<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &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]>,
-    pub mouse_click: SmallVec<[MouseClickListener<V>; 2]>,
-    pub mouse_move: SmallVec<[MouseMoveListener<V>; 2]>,
-    pub scroll_wheel: SmallVec<[ScrollWheelListener<V>; 2]>,
-    pub key_down: SmallVec<[KeyDownListener<V>; 2]>,
-    pub key_up: SmallVec<[KeyUpListener<V>; 2]>,
-}
-
-impl<V> Default for EventListeners<V> {
-    fn default() -> Self {
-        Self {
-            mouse_down: SmallVec::new(),
-            mouse_up: SmallVec::new(),
-            mouse_click: SmallVec::new(),
-            mouse_move: SmallVec::new(),
-            scroll_wheel: SmallVec::new(),
-            key_down: SmallVec::new(),
-            key_up: SmallVec::new(),
-        }
-    }
-}
-
-pub struct MouseClickEvent {
-    pub down: MouseDownEvent,
-    pub up: MouseUpEvent,
-}

crates/gpui3/src/platform.rs 🔗

@@ -5,9 +5,9 @@ mod mac;
 mod test;
 
 use crate::{
-    AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, FontRun,
-    GlobalPixels, GlyphId, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams,
-    RenderSvgParams, Result, Scene, SharedString, Size,
+    AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, FontRun,
+    GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, RenderGlyphParams,
+    RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
@@ -81,7 +81,7 @@ pub(crate) trait Platform: 'static {
     fn on_resign_active(&self, callback: Box<dyn FnMut()>);
     fn on_quit(&self, callback: Box<dyn FnMut()>);
     fn on_reopen(&self, callback: Box<dyn FnMut()>);
-    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
+    fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
 
     fn os_name(&self) -> &'static str;
     fn os_version(&self) -> Result<SemanticVersion>;
@@ -141,7 +141,7 @@ pub(crate) trait PlatformWindow {
     fn minimize(&self);
     fn zoom(&self);
     fn toggle_full_screen(&self);
-    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
+    fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
     fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
     fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
     fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);

crates/gpui3/src/platform/mac/events.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{
-    point, px, Event, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
+    point, px, InputEvent, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
     MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection,
     Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase,
 };
@@ -84,7 +84,7 @@ unsafe fn read_modifiers(native_event: id) -> Modifiers {
     }
 }
 
-impl Event {
+impl InputEvent {
     pub unsafe fn from_native(native_event: id, window_height: Option<Pixels>) -> Option<Self> {
         let event_type = native_event.eventType();
 

crates/gpui3/src/platform/mac/platform.rs 🔗

@@ -1,6 +1,6 @@
 use super::BoolExt;
 use crate::{
-    AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Event, Executor, MacDispatcher,
+    AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Executor, InputEvent, MacDispatcher,
     MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, PathPromptOptions, Platform,
     PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp,
     WindowOptions,
@@ -153,7 +153,7 @@ pub struct MacPlatformState {
     resign_active: Option<Box<dyn FnMut()>>,
     reopen: Option<Box<dyn FnMut()>>,
     quit: Option<Box<dyn FnMut()>>,
-    event: Option<Box<dyn FnMut(Event) -> bool>>,
+    event: Option<Box<dyn FnMut(InputEvent) -> bool>>,
     // menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
     // validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
     will_open_menu: Option<Box<dyn FnMut()>>,
@@ -621,7 +621,7 @@ impl Platform for MacPlatform {
         self.0.lock().reopen = Some(callback);
     }
 
-    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>) {
+    fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
         self.0.lock().event = Some(callback);
     }
 
@@ -937,7 +937,7 @@ unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform {
 
 extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
     unsafe {
-        if let Some(event) = Event::from_native(native_event, None) {
+        if let Some(event) = InputEvent::from_native(native_event, None) {
             let platform = get_foreground_platform(this);
             if let Some(callback) = platform.0.lock().event.as_mut() {
                 if !callback(event) {

crates/gpui3/src/platform/mac/window.rs 🔗

@@ -1,7 +1,7 @@
 use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
 use crate::{
-    display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Event, Executor,
-    GlobalPixels, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
+    display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Executor, GlobalPixels,
+    InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
     MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
     PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer, WindowAppearance,
     WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
@@ -286,7 +286,7 @@ struct MacWindowState {
     renderer: MetalRenderer,
     scene_to_render: Option<Scene>,
     kind: WindowKind,
-    event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
+    event_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
     activate_callback: Option<Box<dyn FnMut(bool)>>,
     resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
     fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
@@ -300,7 +300,7 @@ struct MacWindowState {
     synthetic_drag_counter: usize,
     last_fresh_keydown: Option<Keystroke>,
     traffic_light_position: Option<Point<Pixels>>,
-    previous_modifiers_changed_event: Option<Event>,
+    previous_modifiers_changed_event: Option<InputEvent>,
     // State tracking what the IME did after the last request
     ime_state: ImeState,
     // Retains the last IME Text
@@ -854,7 +854,7 @@ impl PlatformWindow for MacWindow {
             .detach();
     }
 
-    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>) {
+    fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
         self.0.as_ref().lock().event_callback = Some(callback);
     }
 
@@ -975,9 +975,9 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
     let mut lock = window_state.as_ref().lock();
 
     let window_height = lock.content_size().height;
-    let event = unsafe { Event::from_native(native_event, Some(window_height)) };
+    let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
 
-    if let Some(Event::KeyDown(event)) = event {
+    if let Some(InputEvent::KeyDown(event)) = event {
         // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
         // If that event isn't handled, it will then dispatch a "key down" event. GPUI
         // makes no distinction between these two types of events, so we need to ignore
@@ -1045,13 +1045,13 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
                                 key: ime_text.clone().unwrap(),
                             },
                         };
-                        handled = callback(Event::KeyDown(event_with_ime_text));
+                        handled = callback(InputEvent::KeyDown(event_with_ime_text));
                     }
                     if !handled {
                         // empty key happens when you type a deadkey in input composition.
                         // (e.g. on a brazillian keyboard typing quote is a deadkey)
                         if !event.keystroke.key.is_empty() {
-                            handled = callback(Event::KeyDown(event));
+                            handled = callback(InputEvent::KeyDown(event));
                         }
                     }
                 }
@@ -1097,11 +1097,11 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
     let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
 
     let window_height = lock.content_size().height;
-    let event = unsafe { Event::from_native(native_event, Some(window_height)) };
+    let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
 
     if let Some(mut event) = event {
         let synthesized_second_event = match &mut event {
-            Event::MouseDown(
+            InputEvent::MouseDown(
                 event @ MouseDownEvent {
                     button: MouseButton::Left,
                     modifiers: Modifiers { control: true, .. },
@@ -1118,7 +1118,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
                     ..*event
                 };
 
-                Some(Event::MouseDown(MouseDownEvent {
+                Some(InputEvent::MouseDown(MouseDownEvent {
                     button: MouseButton::Right,
                     ..*event
                 }))
@@ -1127,7 +1127,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
             // Because we map a ctrl-left_down to a right_down -> right_up let's ignore
             // the ctrl-left_up to avoid having a mismatch in button down/up events if the
             // user is still holding ctrl when releasing the left mouse button
-            Event::MouseUp(MouseUpEvent {
+            InputEvent::MouseUp(MouseUpEvent {
                 button: MouseButton::Left,
                 modifiers: Modifiers { control: true, .. },
                 ..
@@ -1140,7 +1140,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
         };
 
         match &event {
-            Event::MouseMoved(
+            InputEvent::MouseMoved(
                 event @ MouseMoveEvent {
                     pressed_button: Some(_),
                     ..
@@ -1157,18 +1157,18 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
                     .detach();
             }
 
-            Event::MouseMoved(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
+            InputEvent::MouseMoved(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
 
-            Event::MouseUp(MouseUpEvent {
+            InputEvent::MouseUp(MouseUpEvent {
                 button: MouseButton::Left,
                 ..
             }) => {
                 lock.synthetic_drag_counter += 1;
             }
 
-            Event::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
+            InputEvent::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
                 // Only raise modifiers changed event when they have actually changed
-                if let Some(Event::ModifiersChanged(ModifiersChangedEvent {
+                if let Some(InputEvent::ModifiersChanged(ModifiersChangedEvent {
                     modifiers: prev_modifiers,
                 })) = &lock.previous_modifiers_changed_event
                 {
@@ -1204,7 +1204,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
         modifiers: Default::default(),
         key: ".".into(),
     };
-    let event = Event::KeyDown(KeyDownEvent {
+    let event = InputEvent::KeyDown(KeyDownEvent {
         keystroke: keystroke.clone(),
         is_held: false,
     });
@@ -1605,7 +1605,7 @@ async fn synthetic_drag(
             if lock.synthetic_drag_counter == drag_id {
                 if let Some(mut callback) = lock.event_callback.take() {
                     drop(lock);
-                    callback(Event::MouseMoved(event.clone()));
+                    callback(InputEvent::MouseMoved(event.clone()));
                     window_state.lock().event_callback = Some(callback);
                 }
             } else {

crates/gpui3/src/platform/test.rs 🔗

@@ -125,7 +125,7 @@ impl Platform for TestPlatform {
         unimplemented!()
     }
 
-    fn on_event(&self, _callback: Box<dyn FnMut(crate::Event) -> bool>) {
+    fn on_event(&self, _callback: Box<dyn FnMut(crate::InputEvent) -> bool>) {
         unimplemented!()
     }
 

crates/gpui3/src/window.rs 🔗

@@ -1,12 +1,13 @@
 use crate::{
     px, size, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace, BorrowAppContext,
     Bounds, BoxShadow, Context, Corners, DevicePixels, DisplayId, Edges, Effect, Element, EntityId,
-    Event, EventEmitter, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData, 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, WindowOptions, SUBPIXEL_VARIANTS,
+    EventEmitter, FocusEvent, 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,
+    WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use collections::{HashMap, HashSet};
@@ -47,13 +48,12 @@ pub struct FocusId(usize);
 
 #[derive(Clone)]
 pub struct FocusHandle {
-    id: FocusId,
+    pub(crate) id: FocusId,
 }
 
 impl FocusHandle {
-    pub fn focus(&self, cx: &mut WindowContext) {
-        cx.window.focus = Some(self.id);
-        cx.notify();
+    pub(crate) fn new(id: FocusId) -> Self {
+        Self { id }
     }
 
     pub fn is_focused(&self, cx: &WindowContext) -> bool {
@@ -95,6 +95,8 @@ pub struct Window {
     focus_stack: Vec<FocusStackFrame>,
     focus_parents_by_child: HashMap<FocusId, FocusId>,
     containing_focus: HashSet<FocusId>,
+    pub(crate) focus_change_listeners:
+        Vec<Arc<dyn Fn(&FocusEvent, &mut WindowContext) + Send + Sync + 'static>>,
     key_down_listeners:
         Vec<Arc<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>>,
     key_up_listeners:
@@ -104,7 +106,8 @@ pub struct Window {
     scale_factor: f32,
     pub(crate) scene_builder: SceneBuilder,
     pub(crate) dirty: bool,
-    focus: Option<FocusId>,
+    pub(crate) last_blur: Option<Option<FocusId>>,
+    pub(crate) focus: Option<FocusId>,
     next_focus_id: FocusId,
 }
 
@@ -168,6 +171,7 @@ impl Window {
             focus_parents_by_child: HashMap::default(),
             containing_focus: HashSet::default(),
             mouse_listeners: HashMap::default(),
+            focus_change_listeners: Vec::new(),
             key_down_listeners: Vec::new(),
             key_up_listeners: Vec::new(),
             propagate_event: true,
@@ -175,6 +179,7 @@ impl Window {
             scale_factor,
             scene_builder: SceneBuilder::new(),
             dirty: true,
+            last_blur: None,
             focus: None,
             next_focus_id: FocusId(0),
         }
@@ -232,6 +237,34 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         FocusHandle { id }
     }
 
+    pub fn focus(&mut self, handle: &FocusHandle) {
+        if self.window.last_blur.is_none() {
+            self.window.last_blur = Some(self.window.focus);
+        }
+
+        let window_id = self.window.handle.id;
+        self.window.focus = Some(handle.id);
+        self.push_effect(Effect::FocusChanged {
+            window_id,
+            focused: Some(handle.id),
+        });
+        self.notify();
+    }
+
+    pub fn blur(&mut self) {
+        if self.window.last_blur.is_none() {
+            self.window.last_blur = Some(self.window.focus);
+        }
+
+        let window_id = self.window.handle.id;
+        self.window.focus = None;
+        self.push_effect(Effect::FocusChanged {
+            window_id,
+            focused: None,
+        });
+        self.notify();
+    }
+
     pub fn run_on_main<R>(
         &mut self,
         f: impl FnOnce(&mut MainThread<WindowContext<'_, '_>>) -> R + Send + 'static,
@@ -720,6 +753,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.key_down_listeners.clear();
         window.key_up_listeners.clear();
         window.containing_focus.clear();
@@ -730,7 +764,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         self.text_system().end_frame();
     }
 
-    fn dispatch_event(&mut self, event: Event) -> bool {
+    fn dispatch_event(&mut self, event: InputEvent) -> bool {
         if let Some(any_mouse_event) = event.mouse_event() {
             if let Some(MouseMoveEvent { position, .. }) = any_mouse_event.downcast_ref() {
                 self.window.mouse_position = *position;