diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 3ec9945035..8b52ce5071 100644 --- a/crates/gpui3/src/app.rs +++ b/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 bool + Send + Sync + 'static>; 2]>; +type EventHandlers = + SmallVec<[Arc bool + Send + Sync + 'static>; 2]>; type FrameCallback = Box; pub struct AppContext { @@ -107,6 +110,7 @@ pub struct AppContext { pub(crate) windows: SlotMap>, pub(crate) pending_effects: VecDeque, pub(crate) observers: HashMap, + pub(crate) event_handlers: HashMap, pub(crate) layout_id_buffer: Vec, // 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) { + 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 { pub(crate) enum Effect { Notify(EntityId), + Emit { + entity_id: EntityId, + event: Box, + }, } #[cfg(test)] diff --git a/crates/gpui3/src/app/model_context.rs b/crates/gpui3/src/app/model_context.rs index b16d29057a..895e8edc04 100644 --- a/crates/gpui3/src/app/model_context.rs +++ b/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( + &mut self, + handle: &Handle, + on_event: impl Fn(&mut T, Handle, &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; diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index 4a6864f367..f1868db3c9 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -152,6 +152,10 @@ pub trait BorrowAppContext { } } +pub trait EventEmitter { + type Event: Any + Send + Sync + 'static; +} + pub trait Flatten { fn flatten(self) -> Result; } diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 99ba8dc038..e1db13272e 100644 --- a/crates/gpui3/src/window.rs +++ b/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( + &mut self, + handle: &Handle, + on_event: impl Fn(&mut S, Handle, &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;