Fix draw helper, add helper traits for selecting groupings of input events

Mikayla created

Change summary

crates/gpui/src/app/test_context.rs       |  56 +++++--
crates/gpui/src/elements/list.rs          |  29 +--
crates/gpui/src/interactive.rs            | 170 ++++++++++++++++--------
crates/gpui/src/platform.rs               |   6 
crates/gpui/src/platform/mac/events.rs    |   8 
crates/gpui/src/platform/mac/platform.rs  |  10 
crates/gpui/src/platform/mac/window.rs    |  56 ++++---
crates/gpui/src/platform/test/platform.rs |   2 
crates/gpui/src/platform/test/window.rs   |  14 +-
crates/gpui/src/window.rs                 |  61 ++++----
10 files changed, 243 insertions(+), 169 deletions(-)

Detailed changes

crates/gpui/src/app/test_context.rs 🔗

@@ -1,11 +1,11 @@
 #![deny(missing_docs)]
 
 use crate::{
-    div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
-    BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
-    InputEvent, IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result,
-    Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext,
-    VisualContext, WindowContext, WindowHandle, WindowOptions,
+    Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
+    AvailableSpace, BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter,
+    ForegroundExecutor, InputEvent, Keystroke, Model, ModelContext, Pixels, Platform, Point,
+    Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View,
+    ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
 };
 use anyhow::{anyhow, bail};
 use futures::{Stream, StreamExt};
@@ -167,10 +167,14 @@ impl TestAppContext {
     }
 
     /// Adds a new window with no content.
-    pub fn add_empty_window(&mut self) -> AnyWindowHandle {
+    pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
         let mut cx = self.app.borrow_mut();
-        cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {}))
-            .any_handle
+        let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| ()));
+        drop(cx);
+        let cx = Box::new(VisualTestContext::from_window(*window.deref(), self));
+        cx.run_until_parked();
+        // it might be nice to try and cleanup these at the end of each test.
+        Box::leak(cx)
     }
 
     /// Adds a new window, and returns its root view and a `VisualTestContext` which can be used
@@ -609,9 +613,32 @@ impl<'a> VisualTestContext {
         self.cx.simulate_input(self.window, input)
     }
 
+    /// Draw an element to the window. Useful for simulating events or actions
+    pub fn draw(
+        &mut self,
+        origin: Point<Pixels>,
+        space: Size<AvailableSpace>,
+        f: impl FnOnce(&mut WindowContext) -> AnyElement,
+    ) {
+        self.update(|cx| {
+            let entity_id = cx
+                .window
+                .root_view
+                .as_ref()
+                .expect("Can't draw to this window without a root view")
+                .entity_id();
+            cx.with_view_id(entity_id, |cx| {
+                f(cx).draw(origin, space, cx);
+            });
+
+            cx.refresh();
+        })
+    }
+
     /// Simulate an event from the platform, e.g. a SrollWheelEvent
-    pub fn simulate_event(&mut self, event: InputEvent) {
-        self.update(|cx| cx.dispatch_event(event));
+    /// Make sure you've called [VisualTestContext::draw] first!
+    pub fn simulate_event<E: InputEvent>(&mut self, event: E) {
+        self.update(|cx| cx.dispatch_event(event.to_platform_input()));
         self.background_executor.run_until_parked();
     }
 
@@ -769,12 +796,3 @@ impl AnyWindowHandle {
         self.update(cx, |_, cx| cx.new_view(build_view)).unwrap()
     }
 }
-
-/// An EmptyView for testing.
-pub struct EmptyView {}
-
-impl Render for EmptyView {
-    fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> impl IntoElement {
-        div()
-    }
-}

crates/gpui/src/elements/list.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     point, px, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
-    DispatchPhase, Element, IntoElement, IsZero, Pixels, Point, ScrollWheelEvent, Size, Style,
+    DispatchPhase, Element, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
     StyleRefinement, Styled, WindowContext,
 };
 use collections::VecDeque;
@@ -584,36 +584,33 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
 #[cfg(test)]
 mod test {
 
-    use crate::{self as gpui, Entity, TestAppContext};
+    use gpui::{ScrollDelta, ScrollWheelEvent};
+
+    use crate::{self as gpui, TestAppContext};
 
     #[gpui::test]
     fn test_reset_after_paint_before_scroll(cx: &mut TestAppContext) {
         use crate::{div, list, point, px, size, Element, ListState, Styled};
 
-        let (v, cx) = cx.add_window_view(|_| ());
+        let cx = cx.add_empty_window();
 
         let state = ListState::new(5, crate::ListAlignment::Top, px(10.), |_, _| {
             div().h(px(10.)).w_full().into_any()
         });
 
-        cx.update(|cx| {
-            cx.with_view_id(v.entity_id(), |cx| {
-                list(state.clone())
-                    .w_full()
-                    .h_full()
-                    .z_index(10)
-                    .into_any()
-                    .draw(point(px(0.0), px(0.0)), size(px(100.), px(20.)).into(), cx)
-            });
-        });
+        cx.draw(
+            point(px(0.), px(0.)),
+            size(px(100.), px(20.)).into(),
+            |_| list(state.clone()).w_full().h_full().z_index(10).into_any(),
+        );
 
         state.reset(5);
 
-        cx.simulate_event(gpui::InputEvent::ScrollWheel(gpui::ScrollWheelEvent {
+        cx.simulate_event(ScrollWheelEvent {
             position: point(px(1.), px(1.)),
-            delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-500.))),
+            delta: ScrollDelta::Pixels(point(px(0.), px(-500.))),
             ..Default::default()
-        }));
+        });
 
         assert_eq!(state.logical_scroll_top().item_ix, 0);
         assert_eq!(state.logical_scroll_top().offset_in_item, px(0.));

crates/gpui/src/interactive.rs 🔗

@@ -1,8 +1,14 @@
 use crate::{
-    div, point, Element, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
+    point, seal::Sealed, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
 };
 use smallvec::SmallVec;
-use std::{any::Any, default, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf};
+use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf};
+
+pub trait InputEvent: Sealed + 'static {
+    fn to_platform_input(self) -> PlatformInput;
+}
+pub trait KeyEvent: InputEvent {}
+pub trait MouseEvent: InputEvent {}
 
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct KeyDownEvent {
@@ -10,16 +16,40 @@ pub struct KeyDownEvent {
     pub is_held: bool,
 }
 
+impl Sealed for KeyDownEvent {}
+impl InputEvent for KeyDownEvent {
+    fn to_platform_input(self) -> PlatformInput {
+        PlatformInput::KeyDown(self)
+    }
+}
+impl KeyEvent for KeyDownEvent {}
+
 #[derive(Clone, Debug)]
 pub struct KeyUpEvent {
     pub keystroke: Keystroke,
 }
 
+impl Sealed for KeyUpEvent {}
+impl InputEvent for KeyUpEvent {
+    fn to_platform_input(self) -> PlatformInput {
+        PlatformInput::KeyUp(self)
+    }
+}
+impl KeyEvent for KeyUpEvent {}
+
 #[derive(Clone, Debug, Default)]
 pub struct ModifiersChangedEvent {
     pub modifiers: Modifiers,
 }
 
+impl Sealed for ModifiersChangedEvent {}
+impl InputEvent for ModifiersChangedEvent {
+    fn to_platform_input(self) -> PlatformInput {
+        PlatformInput::ModifiersChanged(self)
+    }
+}
+impl KeyEvent for ModifiersChangedEvent {}
+
 impl Deref for ModifiersChangedEvent {
     type Target = Modifiers;
 
@@ -46,6 +76,14 @@ pub struct MouseDownEvent {
     pub click_count: usize,
 }
 
+impl Sealed for MouseDownEvent {}
+impl InputEvent for MouseDownEvent {
+    fn to_platform_input(self) -> PlatformInput {
+        PlatformInput::MouseDown(self)
+    }
+}
+impl MouseEvent for MouseDownEvent {}
+
 #[derive(Clone, Debug, Default)]
 pub struct MouseUpEvent {
     pub button: MouseButton,
@@ -54,38 +92,20 @@ pub struct MouseUpEvent {
     pub click_count: usize,
 }
 
+impl Sealed for MouseUpEvent {}
+impl InputEvent for MouseUpEvent {
+    fn to_platform_input(self) -> PlatformInput {
+        PlatformInput::MouseUp(self)
+    }
+}
+impl MouseEvent for MouseUpEvent {}
+
 #[derive(Clone, Debug, Default)]
 pub struct ClickEvent {
     pub down: MouseDownEvent,
     pub up: MouseUpEvent,
 }
 
-pub struct Drag<S, R, V, E>
-where
-    R: Fn(&mut V, &mut ViewContext<V>) -> E,
-    V: 'static,
-    E: IntoElement,
-{
-    pub state: S,
-    pub render_drag_handle: R,
-    view_element_types: PhantomData<(V, E)>,
-}
-
-impl<S, R, V, E> Drag<S, R, V, E>
-where
-    R: Fn(&mut V, &mut ViewContext<V>) -> E,
-    V: 'static,
-    E: Element,
-{
-    pub fn new(state: S, render_drag_handle: R) -> Self {
-        Drag {
-            state,
-            render_drag_handle,
-            view_element_types: Default::default(),
-        }
-    }
-}
-
 #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
 pub enum MouseButton {
     Left,
@@ -131,6 +151,14 @@ pub struct MouseMoveEvent {
     pub modifiers: Modifiers,
 }
 
+impl Sealed for MouseMoveEvent {}
+impl InputEvent for MouseMoveEvent {
+    fn to_platform_input(self) -> PlatformInput {
+        PlatformInput::MouseMove(self)
+    }
+}
+impl MouseEvent for MouseMoveEvent {}
+
 impl MouseMoveEvent {
     pub fn dragging(&self) -> bool {
         self.pressed_button == Some(MouseButton::Left)
@@ -145,6 +173,14 @@ pub struct ScrollWheelEvent {
     pub touch_phase: TouchPhase,
 }
 
+impl Sealed for ScrollWheelEvent {}
+impl InputEvent for ScrollWheelEvent {
+    fn to_platform_input(self) -> PlatformInput {
+        PlatformInput::ScrollWheel(self)
+    }
+}
+impl MouseEvent for ScrollWheelEvent {}
+
 impl Deref for ScrollWheelEvent {
     type Target = Modifiers;
 
@@ -202,6 +238,14 @@ pub struct MouseExitEvent {
     pub modifiers: Modifiers,
 }
 
+impl Sealed for MouseExitEvent {}
+impl InputEvent for MouseExitEvent {
+    fn to_platform_input(self) -> PlatformInput {
+        PlatformInput::MouseExited(self)
+    }
+}
+impl MouseEvent for MouseExitEvent {}
+
 impl Deref for MouseExitEvent {
     type Target = Modifiers;
 
@@ -221,7 +265,7 @@ impl ExternalPaths {
 
 impl Render for ExternalPaths {
     fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
-        div() // Intentionally left empty because the platform will render icons for the dragged files
+        () // Intentionally left empty because the platform will render icons for the dragged files
     }
 }
 
@@ -240,8 +284,16 @@ pub enum FileDropEvent {
     Exited,
 }
 
+impl Sealed for FileDropEvent {}
+impl InputEvent for FileDropEvent {
+    fn to_platform_input(self) -> PlatformInput {
+        PlatformInput::FileDrop(self)
+    }
+}
+impl MouseEvent for FileDropEvent {}
+
 #[derive(Clone, Debug)]
-pub enum InputEvent {
+pub enum PlatformInput {
     KeyDown(KeyDownEvent),
     KeyUp(KeyUpEvent),
     ModifiersChanged(ModifiersChangedEvent),
@@ -253,19 +305,19 @@ pub enum InputEvent {
     FileDrop(FileDropEvent),
 }
 
-impl InputEvent {
+impl PlatformInput {
     pub fn position(&self) -> Option<Point<Pixels>> {
         match self {
-            InputEvent::KeyDown { .. } => None,
-            InputEvent::KeyUp { .. } => None,
-            InputEvent::ModifiersChanged { .. } => None,
-            InputEvent::MouseDown(event) => Some(event.position),
-            InputEvent::MouseUp(event) => Some(event.position),
-            InputEvent::MouseMove(event) => Some(event.position),
-            InputEvent::MouseExited(event) => Some(event.position),
-            InputEvent::ScrollWheel(event) => Some(event.position),
-            InputEvent::FileDrop(FileDropEvent::Exited) => None,
-            InputEvent::FileDrop(
+            PlatformInput::KeyDown { .. } => None,
+            PlatformInput::KeyUp { .. } => None,
+            PlatformInput::ModifiersChanged { .. } => None,
+            PlatformInput::MouseDown(event) => Some(event.position),
+            PlatformInput::MouseUp(event) => Some(event.position),
+            PlatformInput::MouseMove(event) => Some(event.position),
+            PlatformInput::MouseExited(event) => Some(event.position),
+            PlatformInput::ScrollWheel(event) => Some(event.position),
+            PlatformInput::FileDrop(FileDropEvent::Exited) => None,
+            PlatformInput::FileDrop(
                 FileDropEvent::Entered { position, .. }
                 | FileDropEvent::Pending { position, .. }
                 | FileDropEvent::Submit { position, .. },
@@ -275,29 +327,29 @@ impl InputEvent {
 
     pub fn mouse_event(&self) -> Option<&dyn Any> {
         match self {
-            InputEvent::KeyDown { .. } => None,
-            InputEvent::KeyUp { .. } => None,
-            InputEvent::ModifiersChanged { .. } => None,
-            InputEvent::MouseDown(event) => Some(event),
-            InputEvent::MouseUp(event) => Some(event),
-            InputEvent::MouseMove(event) => Some(event),
-            InputEvent::MouseExited(event) => Some(event),
-            InputEvent::ScrollWheel(event) => Some(event),
-            InputEvent::FileDrop(event) => Some(event),
+            PlatformInput::KeyDown { .. } => None,
+            PlatformInput::KeyUp { .. } => None,
+            PlatformInput::ModifiersChanged { .. } => None,
+            PlatformInput::MouseDown(event) => Some(event),
+            PlatformInput::MouseUp(event) => Some(event),
+            PlatformInput::MouseMove(event) => Some(event),
+            PlatformInput::MouseExited(event) => Some(event),
+            PlatformInput::ScrollWheel(event) => Some(event),
+            PlatformInput::FileDrop(event) => Some(event),
         }
     }
 
     pub fn keyboard_event(&self) -> Option<&dyn Any> {
         match self {
-            InputEvent::KeyDown(event) => Some(event),
-            InputEvent::KeyUp(event) => Some(event),
-            InputEvent::ModifiersChanged(event) => Some(event),
-            InputEvent::MouseDown(_) => None,
-            InputEvent::MouseUp(_) => None,
-            InputEvent::MouseMove(_) => None,
-            InputEvent::MouseExited(_) => None,
-            InputEvent::ScrollWheel(_) => None,
-            InputEvent::FileDrop(_) => None,
+            PlatformInput::KeyDown(event) => Some(event),
+            PlatformInput::KeyUp(event) => Some(event),
+            PlatformInput::ModifiersChanged(event) => Some(event),
+            PlatformInput::MouseDown(_) => None,
+            PlatformInput::MouseUp(_) => None,
+            PlatformInput::MouseMove(_) => None,
+            PlatformInput::MouseExited(_) => None,
+            PlatformInput::ScrollWheel(_) => None,
+            PlatformInput::FileDrop(_) => None,
         }
     }
 }

crates/gpui/src/platform.rs 🔗

@@ -7,7 +7,7 @@ mod test;
 
 use crate::{
     Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics,
-    FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap, LineLayout, Pixels,
+    FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout, Pixels, PlatformInput,
     Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString,
     Size, TaskLabel,
 };
@@ -88,7 +88,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(InputEvent) -> bool>);
+    fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>);
 
     fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
     fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
@@ -155,7 +155,7 @@ pub trait PlatformWindow {
     fn zoom(&self);
     fn toggle_full_screen(&self);
     fn on_request_frame(&self, callback: Box<dyn FnMut()>);
-    fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
+    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> 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/gpui/src/platform/mac/events.rs 🔗

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

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

@@ -1,8 +1,8 @@
 use super::{events::key_to_native, BoolExt};
 use crate::{
     Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
-    ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker,
-    MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
+    ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
+    MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
     PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions,
 };
 use anyhow::anyhow;
@@ -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(InputEvent) -> bool>>,
+    event: Option<Box<dyn FnMut(PlatformInput) -> 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()>>,
@@ -637,7 +637,7 @@ impl Platform for MacPlatform {
         self.0.lock().reopen = Some(callback);
     }
 
-    fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
+    fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
         self.0.lock().event = Some(callback);
     }
 
@@ -976,7 +976,7 @@ unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform {
 
 extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
     unsafe {
-        if let Some(event) = InputEvent::from_native(native_event, None) {
+        if let Some(event) = PlatformInput::from_native(native_event, None) {
             let platform = get_mac_platform(this);
             let mut lock = platform.0.lock();
             if let Some(mut callback) = lock.event.take() {

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

@@ -1,9 +1,9 @@
 use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
 use crate::{
     display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths,
-    FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke,
-    Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
-    Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
+    FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent, Keystroke, Modifiers,
+    ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
+    PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
     PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
 };
 use block::ConcreteBlock;
@@ -319,7 +319,7 @@ struct MacWindowState {
     renderer: MetalRenderer,
     kind: WindowKind,
     request_frame_callback: Option<Box<dyn FnMut()>>,
-    event_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
+    event_callback: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
     activate_callback: Option<Box<dyn FnMut(bool)>>,
     resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
     fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
@@ -333,7 +333,7 @@ struct MacWindowState {
     synthetic_drag_counter: usize,
     last_fresh_keydown: Option<Keystroke>,
     traffic_light_position: Option<Point<Pixels>>,
-    previous_modifiers_changed_event: Option<InputEvent>,
+    previous_modifiers_changed_event: Option<PlatformInput>,
     // State tracking what the IME did after the last request
     ime_state: ImeState,
     // Retains the last IME Text
@@ -928,7 +928,7 @@ impl PlatformWindow for MacWindow {
         self.0.as_ref().lock().request_frame_callback = Some(callback);
     }
 
-    fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
+    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
         self.0.as_ref().lock().event_callback = Some(callback);
     }
 
@@ -1053,9 +1053,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 { InputEvent::from_native(native_event, Some(window_height)) };
+    let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
 
-    if let Some(InputEvent::KeyDown(event)) = event {
+    if let Some(PlatformInput::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
@@ -1102,7 +1102,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
                         .flatten()
                         .is_some();
                 if !is_composing {
-                    handled = callback(InputEvent::KeyDown(event));
+                    handled = callback(PlatformInput::KeyDown(event));
                 }
 
                 if !handled {
@@ -1146,11 +1146,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 { InputEvent::from_native(native_event, Some(window_height)) };
+    let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
 
     if let Some(mut event) = event {
         match &mut event {
-            InputEvent::MouseDown(
+            PlatformInput::MouseDown(
                 event @ MouseDownEvent {
                     button: MouseButton::Left,
                     modifiers: Modifiers { control: true, .. },
@@ -1172,7 +1172,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
-            InputEvent::MouseUp(
+            PlatformInput::MouseUp(
                 event @ MouseUpEvent {
                     button: MouseButton::Left,
                     modifiers: Modifiers { control: true, .. },
@@ -1194,7 +1194,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
         };
 
         match &event {
-            InputEvent::MouseMove(
+            PlatformInput::MouseMove(
                 event @ MouseMoveEvent {
                     pressed_button: Some(_),
                     ..
@@ -1216,15 +1216,15 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
                 }
             }
 
-            InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
+            PlatformInput::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
 
-            InputEvent::MouseUp(MouseUpEvent { .. }) => {
+            PlatformInput::MouseUp(MouseUpEvent { .. }) => {
                 lock.synthetic_drag_counter += 1;
             }
 
-            InputEvent::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
+            PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
                 // Only raise modifiers changed event when they have actually changed
-                if let Some(InputEvent::ModifiersChanged(ModifiersChangedEvent {
+                if let Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
                     modifiers: prev_modifiers,
                 })) = &lock.previous_modifiers_changed_event
                 {
@@ -1258,7 +1258,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
         key: ".".into(),
         ime_key: None,
     };
-    let event = InputEvent::KeyDown(KeyDownEvent {
+    let event = PlatformInput::KeyDown(KeyDownEvent {
         keystroke: keystroke.clone(),
         is_held: false,
     });
@@ -1655,7 +1655,7 @@ extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDr
     if send_new_event(&window_state, {
         let position = drag_event_position(&window_state, dragging_info);
         let paths = external_paths_from_event(dragging_info);
-        InputEvent::FileDrop(FileDropEvent::Entered { position, paths })
+        PlatformInput::FileDrop(FileDropEvent::Entered { position, paths })
     }) {
         window_state.lock().external_files_dragged = true;
         NSDragOperationCopy
@@ -1669,7 +1669,7 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr
     let position = drag_event_position(&window_state, dragging_info);
     if send_new_event(
         &window_state,
-        InputEvent::FileDrop(FileDropEvent::Pending { position }),
+        PlatformInput::FileDrop(FileDropEvent::Pending { position }),
     ) {
         NSDragOperationCopy
     } else {
@@ -1679,7 +1679,10 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr
 
 extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
     let window_state = unsafe { get_window_state(this) };
-    send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited));
+    send_new_event(
+        &window_state,
+        PlatformInput::FileDrop(FileDropEvent::Exited),
+    );
     window_state.lock().external_files_dragged = false;
 }
 
@@ -1688,7 +1691,7 @@ extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -
     let position = drag_event_position(&window_state, dragging_info);
     if send_new_event(
         &window_state,
-        InputEvent::FileDrop(FileDropEvent::Submit { position }),
+        PlatformInput::FileDrop(FileDropEvent::Submit { position }),
     ) {
         YES
     } else {
@@ -1712,7 +1715,10 @@ fn external_paths_from_event(dragging_info: *mut Object) -> ExternalPaths {
 
 extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {
     let window_state = unsafe { get_window_state(this) };
-    send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited));
+    send_new_event(
+        &window_state,
+        PlatformInput::FileDrop(FileDropEvent::Exited),
+    );
 }
 
 async fn synthetic_drag(
@@ -1727,7 +1733,7 @@ async fn synthetic_drag(
             if lock.synthetic_drag_counter == drag_id {
                 if let Some(mut callback) = lock.event_callback.take() {
                     drop(lock);
-                    callback(InputEvent::MouseMove(event.clone()));
+                    callback(PlatformInput::MouseMove(event.clone()));
                     window_state.lock().event_callback = Some(callback);
                 }
             } else {
@@ -1737,7 +1743,7 @@ async fn synthetic_drag(
     }
 }
 
-fn send_new_event(window_state_lock: &Mutex<MacWindowState>, e: InputEvent) -> bool {
+fn send_new_event(window_state_lock: &Mutex<MacWindowState>, e: PlatformInput) -> bool {
     let window_state = window_state_lock.lock().event_callback.take();
     if let Some(mut callback) = window_state {
         callback(e);

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

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

crates/gpui/src/platform/test/window.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
-    px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, InputEvent, KeyDownEvent,
-    Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
-    Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
+    px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, KeyDownEvent, Keystroke,
+    Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
+    Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
 };
 use collections::HashMap;
 use parking_lot::Mutex;
@@ -19,7 +19,7 @@ pub struct TestWindowState {
     platform: Weak<TestPlatform>,
     sprite_atlas: Arc<dyn PlatformAtlas>,
     pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
-    input_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
+    input_callback: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
     active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
     resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
     moved_callback: Option<Box<dyn FnMut()>>,
@@ -85,7 +85,7 @@ impl TestWindow {
         self.0.lock().active_status_change_callback = Some(callback);
     }
 
-    pub fn simulate_input(&mut self, event: InputEvent) -> bool {
+    pub fn simulate_input(&mut self, event: PlatformInput) -> bool {
         let mut lock = self.0.lock();
         let Some(mut callback) = lock.input_callback.take() else {
             return false;
@@ -97,7 +97,7 @@ impl TestWindow {
     }
 
     pub fn simulate_keystroke(&mut self, keystroke: Keystroke, is_held: bool) {
-        if self.simulate_input(InputEvent::KeyDown(KeyDownEvent {
+        if self.simulate_input(PlatformInput::KeyDown(KeyDownEvent {
             keystroke: keystroke.clone(),
             is_held,
         })) {
@@ -220,7 +220,7 @@ impl PlatformWindow for TestWindow {
 
     fn on_request_frame(&self, _callback: Box<dyn FnMut()>) {}
 
-    fn on_input(&self, callback: Box<dyn FnMut(crate::InputEvent) -> bool>) {
+    fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> bool>) {
         self.0.lock().input_callback = Some(callback)
     }
 

crates/gpui/src/window.rs 🔗

@@ -5,13 +5,14 @@ use crate::{
     AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
     DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect,
     Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla,
-    ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId,
-    Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent,
-    Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
-    PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
-    RenderSvgParams, ScaledPixels, Scene, Shadow, SharedString, Size, Style, SubscriberSet,
-    Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
-    WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+    ImageData, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, KeystrokeEvent, LayoutId,
+    Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent,
+    MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
+    PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render,
+    RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, Scene, Shadow,
+    SharedString, Size, Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task,
+    Underline, UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions,
+    SUBPIXEL_VARIANTS,
 };
 use anyhow::{anyhow, Context as _, Result};
 use collections::{FxHashMap, FxHashSet};
@@ -968,7 +969,7 @@ impl<'a> WindowContext<'a> {
     /// Register a mouse event listener on the window for the next 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.
-    pub fn on_mouse_event<Event: 'static>(
+    pub fn on_mouse_event<Event: MouseEvent>(
         &mut self,
         mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static,
     ) {
@@ -996,7 +997,7 @@ impl<'a> WindowContext<'a> {
     ///
     /// 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<Event: 'static>(
+    pub fn on_key_event<Event: KeyEvent>(
         &mut self,
         listener: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
     ) {
@@ -1617,7 +1618,7 @@ impl<'a> WindowContext<'a> {
     }
 
     /// Dispatch a mouse or keyboard event on the window.
-    pub fn dispatch_event(&mut self, event: InputEvent) -> bool {
+    pub fn dispatch_event(&mut self, event: PlatformInput) -> bool {
         // Handlers may set this to false by calling `stop_propagation`.
         self.app.propagate_event = true;
         // Handlers may set this to true by calling `prevent_default`.
@@ -1626,37 +1627,37 @@ impl<'a> WindowContext<'a> {
         let event = match event {
             // Track the mouse position with our own state, since accessing the platform
             // API for the mouse position can only occur on the main thread.
-            InputEvent::MouseMove(mouse_move) => {
+            PlatformInput::MouseMove(mouse_move) => {
                 self.window.mouse_position = mouse_move.position;
                 self.window.modifiers = mouse_move.modifiers;
-                InputEvent::MouseMove(mouse_move)
+                PlatformInput::MouseMove(mouse_move)
             }
-            InputEvent::MouseDown(mouse_down) => {
+            PlatformInput::MouseDown(mouse_down) => {
                 self.window.mouse_position = mouse_down.position;
                 self.window.modifiers = mouse_down.modifiers;
-                InputEvent::MouseDown(mouse_down)
+                PlatformInput::MouseDown(mouse_down)
             }
-            InputEvent::MouseUp(mouse_up) => {
+            PlatformInput::MouseUp(mouse_up) => {
                 self.window.mouse_position = mouse_up.position;
                 self.window.modifiers = mouse_up.modifiers;
-                InputEvent::MouseUp(mouse_up)
+                PlatformInput::MouseUp(mouse_up)
             }
-            InputEvent::MouseExited(mouse_exited) => {
+            PlatformInput::MouseExited(mouse_exited) => {
                 self.window.modifiers = mouse_exited.modifiers;
-                InputEvent::MouseExited(mouse_exited)
+                PlatformInput::MouseExited(mouse_exited)
             }
-            InputEvent::ModifiersChanged(modifiers_changed) => {
+            PlatformInput::ModifiersChanged(modifiers_changed) => {
                 self.window.modifiers = modifiers_changed.modifiers;
-                InputEvent::ModifiersChanged(modifiers_changed)
+                PlatformInput::ModifiersChanged(modifiers_changed)
             }
-            InputEvent::ScrollWheel(scroll_wheel) => {
+            PlatformInput::ScrollWheel(scroll_wheel) => {
                 self.window.mouse_position = scroll_wheel.position;
                 self.window.modifiers = scroll_wheel.modifiers;
-                InputEvent::ScrollWheel(scroll_wheel)
+                PlatformInput::ScrollWheel(scroll_wheel)
             }
             // Translate dragging and dropping of external files from the operating system
             // to internal drag and drop events.
-            InputEvent::FileDrop(file_drop) => match file_drop {
+            PlatformInput::FileDrop(file_drop) => match file_drop {
                 FileDropEvent::Entered { position, paths } => {
                     self.window.mouse_position = position;
                     if self.active_drag.is_none() {
@@ -1666,7 +1667,7 @@ impl<'a> WindowContext<'a> {
                             cursor_offset: position,
                         });
                     }
-                    InputEvent::MouseMove(MouseMoveEvent {
+                    PlatformInput::MouseMove(MouseMoveEvent {
                         position,
                         pressed_button: Some(MouseButton::Left),
                         modifiers: Modifiers::default(),
@@ -1674,7 +1675,7 @@ impl<'a> WindowContext<'a> {
                 }
                 FileDropEvent::Pending { position } => {
                     self.window.mouse_position = position;
-                    InputEvent::MouseMove(MouseMoveEvent {
+                    PlatformInput::MouseMove(MouseMoveEvent {
                         position,
                         pressed_button: Some(MouseButton::Left),
                         modifiers: Modifiers::default(),
@@ -1683,21 +1684,21 @@ impl<'a> WindowContext<'a> {
                 FileDropEvent::Submit { position } => {
                     self.activate(true);
                     self.window.mouse_position = position;
-                    InputEvent::MouseUp(MouseUpEvent {
+                    PlatformInput::MouseUp(MouseUpEvent {
                         button: MouseButton::Left,
                         position,
                         modifiers: Modifiers::default(),
                         click_count: 1,
                     })
                 }
-                FileDropEvent::Exited => InputEvent::MouseUp(MouseUpEvent {
+                FileDropEvent::Exited => PlatformInput::MouseUp(MouseUpEvent {
                     button: MouseButton::Left,
                     position: Point::default(),
                     modifiers: Modifiers::default(),
                     click_count: 1,
                 }),
             },
-            InputEvent::KeyDown(_) | InputEvent::KeyUp(_) => event,
+            PlatformInput::KeyDown(_) | PlatformInput::KeyUp(_) => event,
         };
 
         if let Some(any_mouse_event) = event.mouse_event() {
@@ -2976,7 +2977,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
     /// Add a listener for any mouse event that occurs in the window.
     /// This is a fairly low level method.
     /// Typically, you'll want to use methods on UI elements, which perform bounds checking etc.
-    pub fn on_mouse_event<Event: 'static>(
+    pub fn on_mouse_event<Event: MouseEvent>(
         &mut self,
         handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
     ) {
@@ -2989,7 +2990,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
     }
 
     /// Register a callback to be invoked when the given Key Event is dispatched to the window.
-    pub fn on_key_event<Event: 'static>(
+    pub fn on_key_event<Event: KeyEvent>(
         &mut self,
         handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
     ) {