Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/app.rs               | 19 +++++++++++
crates/gpui3/src/app/model_context.rs | 36 ++++++++++++++++++++
crates/gpui3/src/gpui3.rs             |  4 ++
crates/gpui3/src/window.rs            | 49 ++++++++++++++++++++++++++--
4 files changed, 102 insertions(+), 6 deletions(-)

Detailed changes

crates/gpui3/src/app.rs 🔗

@@ -68,6 +68,7 @@ impl App {
                 windows: SlotMap::with_key(),
                 pending_effects: Default::default(),
                 observers: Default::default(),
+                event_handlers: Default::default(),
                 layout_id_buffer: Default::default(),
             })
         }))
@@ -88,6 +89,8 @@ impl App {
 }
 
 type Handlers = SmallVec<[Arc<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
+type EventHandlers =
+    SmallVec<[Arc<dyn Fn(&dyn Any, &mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
 type FrameCallback = Box<dyn FnOnce(&mut WindowContext) + Send>;
 
 pub struct AppContext {
@@ -107,6 +110,7 @@ pub struct AppContext {
     pub(crate) windows: SlotMap<WindowId, Option<Window>>,
     pub(crate) pending_effects: VecDeque<Effect>,
     pub(crate) observers: HashMap<EntityId, Handlers>,
+    pub(crate) event_handlers: HashMap<EntityId, EventHandlers>,
     pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
 }
 
@@ -149,6 +153,7 @@ impl AppContext {
         while let Some(effect) = self.pending_effects.pop_front() {
             match effect {
                 Effect::Notify(entity_id) => self.apply_notify_effect(entity_id),
+                Effect::Emit { entity_id, event } => self.apply_emit_effect(entity_id, event),
             }
         }
 
@@ -180,6 +185,16 @@ impl AppContext {
         }
     }
 
+    fn apply_emit_effect(&mut self, updated_entity: EntityId, event: Box<dyn Any>) {
+        if let Some(mut handlers) = self.event_handlers.remove(&updated_entity) {
+            handlers.retain(|handler| handler(&event, self));
+            if let Some(new_handlers) = self.event_handlers.remove(&updated_entity) {
+                handlers.extend(new_handlers);
+            }
+            self.event_handlers.insert(updated_entity, handlers);
+        }
+    }
+
     pub fn to_async(&self) -> AsyncAppContext {
         AsyncAppContext(unsafe { mem::transmute(self.this.clone()) })
     }
@@ -367,6 +382,10 @@ impl MainThread<AppContext> {
 
 pub(crate) enum Effect {
     Notify(EntityId),
+    Emit {
+        entity_id: EntityId,
+        event: Box<dyn Any + Send + Sync + 'static>,
+    },
 }
 
 #[cfg(test)]

crates/gpui3/src/app/model_context.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{AppContext, Context, Effect, EntityId, Handle, Reference, WeakHandle};
+use crate::{AppContext, Context, Effect, EntityId, EventEmitter, Handle, Reference, WeakHandle};
 use std::{marker::PhantomData, sync::Arc};
 
 pub struct ModelContext<'a, T> {
@@ -59,6 +59,31 @@ impl<'a, T: Send + Sync + 'static> ModelContext<'a, T> {
             }));
     }
 
+    pub fn subscribe<E: EventEmitter + Send + Sync + 'static>(
+        &mut self,
+        handle: &Handle<E>,
+        on_event: impl Fn(&mut T, Handle<E>, &E::Event, &mut ModelContext<'_, T>)
+            + Send
+            + Sync
+            + 'static,
+    ) {
+        let this = self.handle();
+        let handle = handle.downgrade();
+        self.app
+            .event_handlers
+            .entry(handle.id)
+            .or_default()
+            .push(Arc::new(move |event, cx| {
+                let event = event.downcast_ref().expect("invalid event type");
+                if let Some((this, handle)) = this.upgrade(cx).zip(handle.upgrade(cx)) {
+                    this.update(cx, |this, cx| on_event(this, handle, event, cx));
+                    true
+                } else {
+                    false
+                }
+            }));
+    }
+
     pub fn notify(&mut self) {
         self.app
             .pending_effects
@@ -66,6 +91,15 @@ impl<'a, T: Send + Sync + 'static> ModelContext<'a, T> {
     }
 }
 
+impl<'a, T: EventEmitter + Send + Sync + 'static> ModelContext<'a, T> {
+    pub fn emit(&mut self, event: T::Event) {
+        self.app.pending_effects.push_back(Effect::Emit {
+            entity_id: self.entity_id,
+            event: Box::new(event),
+        });
+    }
+}
+
 impl<'a, T: 'static> Context for ModelContext<'a, T> {
     type EntityContext<'b, 'c, U: Send + Sync + 'static> = ModelContext<'b, U>;
     type Result<U> = U;

crates/gpui3/src/gpui3.rs 🔗

@@ -152,6 +152,10 @@ pub trait BorrowAppContext {
     }
 }
 
+pub trait EventEmitter {
+    type Event: Any + Send + Sync + 'static;
+}
+
 pub trait Flatten<T> {
     fn flatten(self) -> Result<T>;
 }

crates/gpui3/src/window.rs 🔗

@@ -1,11 +1,11 @@
 use crate::{
     px, size, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace, BorrowAppContext,
     Bounds, BoxShadow, Context, Corners, DevicePixels, DisplayId, Edges, Effect, Element, EntityId,
-    Event, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData, IsZero, LayoutId, MainThread,
-    MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path, Pixels, PlatformAtlas, PlatformWindow,
-    Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
-    RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style,
-    TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, WindowOptions,
+    Event, EventEmitter, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData, IsZero,
+    LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path, Pixels,
+    PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams,
+    RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
+    Style, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, WindowOptions,
     SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
@@ -930,6 +930,35 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
             }));
     }
 
+    pub fn subscribe<E: EventEmitter + Send + Sync + 'static>(
+        &mut self,
+        handle: &Handle<E>,
+        on_event: impl Fn(&mut S, Handle<E>, &E::Event, &mut ViewContext<'_, '_, S>)
+            + Send
+            + Sync
+            + 'static,
+    ) {
+        let this = self.handle();
+        let handle = handle.downgrade();
+        let window_handle = self.window.handle;
+        self.app
+            .event_handlers
+            .entry(handle.id)
+            .or_default()
+            .push(Arc::new(move |event, cx| {
+                cx.update_window(window_handle.id, |cx| {
+                    if let Some(handle) = handle.upgrade(cx) {
+                        let event = event.downcast_ref().expect("invalid event type");
+                        this.update(cx, |this, cx| on_event(this, handle, event, cx))
+                            .is_ok()
+                    } else {
+                        false
+                    }
+                })
+                .unwrap_or(false)
+            }));
+    }
+
     pub fn notify(&mut self) {
         self.window_cx.notify();
         self.window_cx
@@ -983,6 +1012,16 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
     }
 }
 
+impl<'a, 'w, S: EventEmitter + Send + Sync + 'static> ViewContext<'a, 'w, S> {
+    pub fn emit(&mut self, event: S::Event) {
+        let entity_id = self.entity_id;
+        self.app.pending_effects.push_back(Effect::Emit {
+            entity_id,
+            event: Box::new(event),
+        });
+    }
+}
+
 impl<'a, 'w, S> Context for ViewContext<'a, 'w, S> {
     type EntityContext<'b, 'c, U: 'static + Send + Sync> = ViewContext<'b, 'c, U>;
     type Result<U> = U;