From 733df38f9b3aaeffc74ee70eb655614d711dd3d3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 22 Aug 2023 10:42:26 -0600 Subject: [PATCH] Checkpoint Co-Authored-By: Antonio Scandurra --- crates/gpui/playground/src/adapter.rs | 2 +- crates/gpui/playground/src/div.rs | 39 ++---- crates/gpui/playground/src/element.rs | 148 ++++++++++---------- crates/gpui/playground/src/hoverable.rs | 26 ++-- crates/gpui/playground/src/interactive.rs | 34 +++++ crates/gpui/playground/src/paint_context.rs | 52 ++----- crates/gpui/playground/src/playground.rs | 1 + crates/gpui/src/app.rs | 37 +++-- crates/gpui/src/app/window.rs | 39 ++---- crates/gpui/src/scene.rs | 32 +---- 10 files changed, 199 insertions(+), 211 deletions(-) create mode 100644 crates/gpui/playground/src/interactive.rs diff --git a/crates/gpui/playground/src/adapter.rs b/crates/gpui/playground/src/adapter.rs index 392186e295..7a5a0399a8 100644 --- a/crates/gpui/playground/src/adapter.rs +++ b/crates/gpui/playground/src/adapter.rs @@ -47,7 +47,7 @@ impl gpui::Element for AdapterElement { let (layout_engine, layout_id) = layout_data.take().unwrap(); legacy_cx.push_layout_engine(layout_engine); let mut cx = PaintContext::new(legacy_cx, scene); - self.0.paint(view, layout_id, &mut cx); + self.0.paint(view, &mut cx); *layout_data = legacy_cx.pop_layout_engine().zip(Some(layout_id)); debug_assert!(layout_data.is_some()); } diff --git a/crates/gpui/playground/src/div.rs b/crates/gpui/playground/src/div.rs index 37bd69376d..8efe359025 100644 --- a/crates/gpui/playground/src/div.rs +++ b/crates/gpui/playground/src/div.rs @@ -1,22 +1,24 @@ use crate::{ element::{AnyElement, Element, Layout, ParentElement}, + interactive::{InteractionHandlers, Interactive}, layout_context::LayoutContext, paint_context::PaintContext, style::{Style, StyleHelpers, StyleRefinement, Styleable}, }; use anyhow::Result; -use gpui::{platform::MouseMovedEvent, EventContext, LayoutId}; +use gpui::LayoutId; use smallvec::SmallVec; -use std::rc::Rc; -pub struct Div { +pub struct Div { style: StyleRefinement, + handlers: InteractionHandlers, children: SmallVec<[AnyElement; 2]>, } pub fn div() -> Div { Div { style: Default::default(), + handlers: Default::default(), children: Default::default(), } } @@ -44,45 +46,34 @@ impl Element for Div { let style = self.style(); style.paint_background::(layout, cx); + for child in &mut self.children { + child.paint(view, cx); + } } } impl Styleable for Div { type Style = Style; - fn declared_style(&mut self) -> &mut crate::style::StyleRefinement { + fn declared_style(&mut self) -> &mut StyleRefinement { &mut self.style } } impl StyleHelpers for Div {} +impl Interactive for Div { + fn interaction_handlers(&mut self) -> &mut InteractionHandlers { + &mut self.handlers + } +} + impl ParentElement for Div { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { &mut self.children } } -pub trait Interactive { - fn declared_interactions(&mut self) -> &mut Interactions; - - fn on_mouse_move(mut self, handler: H) -> Self - where - H: 'static + Fn(&mut V, &MouseMovedEvent, &mut EventContext), - Self: Sized, - { - self.declared_interactions().mouse_moved = Some(Rc::new(move |view, event, cx| { - handler(view, event, cx); - cx.bubble - })); - self - } -} - -pub struct Interactions { - mouse_moved: Option) -> bool>>, -} - #[test] fn test() { // let elt = div().w_auto(); diff --git a/crates/gpui/playground/src/element.rs b/crates/gpui/playground/src/element.rs index 24ad7bcfda..082f3b02b0 100644 --- a/crates/gpui/playground/src/element.rs +++ b/crates/gpui/playground/src/element.rs @@ -10,6 +10,79 @@ pub use crate::paint_context::PaintContext; type LayoutId = gpui::LayoutId; +pub trait Element: 'static { + type Layout; + + fn layout( + &mut self, + view: &mut V, + cx: &mut LayoutContext, + ) -> Result> + where + Self: Sized; + + fn paint( + &mut self, + view: &mut V, + layout: &mut Layout, + cx: &mut PaintContext, + ) where + Self: Sized; + + fn into_any(self) -> AnyElement + where + Self: 'static + Sized, + { + AnyElement(Box::new(ElementState { + element: self, + layout: None, + })) + } +} + +/// Used to make ElementState into a trait object, so we can wrap it in AnyElement. +trait ElementStateObject { + fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result; + fn paint(&mut self, view: &mut V, cx: &mut PaintContext); +} + +/// A wrapper around an element that stores its layout state. +struct ElementState> { + element: E, + layout: Option>, +} + +/// We blanket-implement the object-safe ElementStateObject interface to make ElementStates into trait objects +impl> ElementStateObject for ElementState { + fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result { + let layout = self.element.layout(view, cx)?; + let layout_id = layout.id; + self.layout = Some(layout); + Ok(layout_id) + } + + fn paint(&mut self, view: &mut V, cx: &mut PaintContext) { + let layout = self.layout.as_mut().expect("paint called before layout"); + if layout.engine_layout.is_none() { + layout.engine_layout = cx.computed_layout(layout.id).log_err() + } + self.element.paint(view, layout, cx) + } +} + +/// A dynamic element. +pub struct AnyElement(Box>); + +impl AnyElement { + pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result { + self.0.layout(view, cx) + } + + pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext) { + self.0.paint(view, cx) + } +} + #[derive(Deref, DerefMut)] pub struct Layout { id: LayoutId, @@ -47,84 +120,11 @@ impl Layout { impl Layout>> { pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext) { let mut element = self.element_data.take().unwrap(); - element.paint(view, self.id, cx); + element.paint(view, cx); self.element_data = Some(element); } } -pub trait Element: 'static { - type Layout; - - fn layout( - &mut self, - view: &mut V, - cx: &mut LayoutContext, - ) -> Result> - where - Self: Sized; - - fn paint( - &mut self, - view: &mut V, - layout: &mut Layout, - cx: &mut PaintContext, - ) where - Self: Sized; - - fn into_any(self) -> AnyElement - where - Self: 'static + Sized, - { - AnyElement(Box::new(ElementState { - element: self, - layout: None, - })) - } -} - -/// Used to make ElementState into a trait object, so we can wrap it in AnyElement. -trait ElementStateObject { - fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result; - fn paint(&mut self, view: &mut V, layout_id: LayoutId, cx: &mut PaintContext); -} - -/// A wrapper around an element that stores its layout state. -struct ElementState> { - element: E, - layout: Option>, -} - -/// We blanket-implement the object-safe ElementStateObject interface to make ElementStates into trait objects -impl> ElementStateObject for ElementState { - fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result { - let layout = self.element.layout(view, cx)?; - let layout_id = layout.id; - self.layout = Some(layout); - Ok(layout_id) - } - - fn paint(&mut self, view: &mut V, layout_id: LayoutId, cx: &mut PaintContext) { - let layout = self.layout.as_mut().expect("paint called before layout"); - if layout.engine_layout.is_none() { - layout.engine_layout = cx.computed_layout(layout_id).log_err() - } - self.element.paint(view, layout, cx) - } -} - -/// A dynamic element. -pub struct AnyElement(Box>); - -impl AnyElement { - pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result { - self.0.layout(view, cx) - } - - pub fn paint(&mut self, view: &mut V, layout_id: LayoutId, cx: &mut PaintContext) { - self.0.paint(view, layout_id, cx) - } -} - pub trait ParentElement { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]>; diff --git a/crates/gpui/playground/src/hoverable.rs b/crates/gpui/playground/src/hoverable.rs index 5335850870..5545155a60 100644 --- a/crates/gpui/playground/src/hoverable.rs +++ b/crates/gpui/playground/src/hoverable.rs @@ -42,14 +42,6 @@ impl + Styleable> Element for Hoverable { where Self: Sized, { - if self.hovered.get() { - // If hovered, refine the child's style with this element's style. - self.child.declared_style().refine(&self.hovered_style); - } else { - // Otherwise, set the child's style back to its original style. - *self.child.declared_style() = self.child_style.clone(); - } - self.child.layout(view, cx) } @@ -61,10 +53,24 @@ impl + Styleable> Element for Hoverable { ) where Self: Sized, { + if self.hovered.get() { + // If hovered, refine the child's style with this element's style. + self.child.declared_style().refine(&self.hovered_style); + } else { + // Otherwise, set the child's style back to its original style. + *self.child.declared_style() = self.child_style.clone(); + } + let bounds = layout.bounds(cx); let order = layout.order(cx); self.hovered.set(bounds.contains_point(cx.mouse_position())); - let hovered = self.hovered.clone(); - cx.on_event(order, move |view, event: &MouseMovedEvent, cx| {}); + let was_hovered = self.hovered.clone(); + cx.on_event(order, move |view, event: &MouseMovedEvent, cx| { + let is_hovered = bounds.contains_point(event.position); + if is_hovered != was_hovered.get() { + was_hovered.set(is_hovered); + cx.repaint(); + } + }); } } diff --git a/crates/gpui/playground/src/interactive.rs b/crates/gpui/playground/src/interactive.rs new file mode 100644 index 0000000000..8debcb1692 --- /dev/null +++ b/crates/gpui/playground/src/interactive.rs @@ -0,0 +1,34 @@ +use gpui::{platform::MouseMovedEvent, EventContext}; +use smallvec::SmallVec; +use std::rc::Rc; + +pub trait Interactive { + fn interaction_handlers(&mut self) -> &mut InteractionHandlers; + + fn on_mouse_move(mut self, handler: H) -> Self + where + H: 'static + Fn(&mut V, &MouseMovedEvent, bool, &mut EventContext), + Self: Sized, + { + self.interaction_handlers() + .mouse_moved + .push(Rc::new(move |view, event, hit_test, cx| { + handler(view, event, hit_test, cx); + cx.bubble + })); + self + } +} + +pub struct InteractionHandlers { + mouse_moved: + SmallVec<[Rc) -> bool>; 2]>, +} + +impl Default for InteractionHandlers { + fn default() -> Self { + Self { + mouse_moved: Default::default(), + } + } +} diff --git a/crates/gpui/playground/src/paint_context.rs b/crates/gpui/playground/src/paint_context.rs index 59e728b6f7..d853aff7f8 100644 --- a/crates/gpui/playground/src/paint_context.rs +++ b/crates/gpui/playground/src/paint_context.rs @@ -1,9 +1,6 @@ use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; -use gpui::{ - geometry::rect::RectF, scene::EventHandler, EngineLayout, EventContext, LayoutId, - RenderContext, ViewContext, -}; +use gpui::{scene::EventHandler, EngineLayout, EventContext, LayoutId, RenderContext, ViewContext}; pub use gpui::{LayoutContext, PaintContext as LegacyPaintContext}; use std::{any::TypeId, rc::Rc}; pub use taffy::tree::NodeId; @@ -47,48 +44,25 @@ impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> { order: u32, handler: impl Fn(&mut V, &E, &mut ViewContext) + 'static, ) { + let view = self.weak_handle(); + self.scene.event_handlers.push(EventHandler { order, - handler: Rc::new(move |view, event, window_cx, view_id| { - let mut view_context = ViewContext::mutable(window_cx, view_id); - handler( - view.downcast_mut().unwrap(), - event.downcast_ref().unwrap(), - &mut view_context, - ); + handler: Rc::new(move |event, window_cx| { + if let Some(view) = view.upgrade(window_cx) { + view.update(window_cx, |view, view_cx| { + let mut event_cx = EventContext::new(view_cx); + handler(view, event.downcast_ref().unwrap(), &mut event_cx); + event_cx.bubble + }) + } else { + true + } }), event_type: TypeId::of::(), }) } - pub fn draw_interactive_region( - &mut self, - order: u32, - bounds: RectF, - outside_bounds: bool, - event_handler: impl Fn(&mut V, &E, &mut EventContext) + 'static, - ) { - // We'll sort these later when `take_interactive_regions` is called. - self.scene - .interactive_regions - .push(gpui::scene::InteractiveRegion { - order, - bounds, - outside_bounds, - event_handler: Rc::new(move |view, event, window_cx, view_id| { - let mut view_context = ViewContext::mutable(window_cx, view_id); - let mut event_context = EventContext::new(&mut view_context); - event_handler( - view.downcast_mut().unwrap(), - event.downcast_ref().unwrap(), - &mut event_context, - ); - }), - event_type: TypeId::of::(), - view_id: self.view_id(), - }); - } - pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result { self.layout_engine() .ok_or_else(|| anyhow!("no layout engine present"))? diff --git a/crates/gpui/playground/src/playground.rs b/crates/gpui/playground/src/playground.rs index 9bb0b9a8a8..2462ac99f5 100644 --- a/crates/gpui/playground/src/playground.rs +++ b/crates/gpui/playground/src/playground.rs @@ -16,6 +16,7 @@ mod components; mod div; mod element; mod hoverable; +mod interactive; mod layout_context; mod paint_context; mod style; diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 941e09c1d3..d6a5367e40 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -4680,18 +4680,37 @@ impl WeakViewHandle { }) } - pub fn update( + pub fn update( &self, - cx: &mut AsyncAppContext, + cx: &mut B, update: impl FnOnce(&mut V, &mut ViewContext) -> T, - ) -> Result { - cx.update(|cx| { - let handle = cx - .upgrade_view_handle(self) - .ok_or_else(|| anyhow!("view was dropped"))?; - cx.update_window(self.window, |cx| handle.update(cx, update)) - .ok_or_else(|| anyhow!("window was removed")) + ) -> Result + where + B: BorrowWindowContext, + B::Result>: Flatten, + { + cx.update_window(self.window(), |cx| { + cx.upgrade_view_handle(self) + .map(|handle| handle.update(cx, update)) }) + .flatten() + .ok_or_else(|| anyhow!("window was removed")) + } +} + +pub trait Flatten { + fn flatten(self) -> Option; +} + +impl Flatten for Option> { + fn flatten(self) -> Option { + self.flatten() + } +} + +impl Flatten for Option { + fn flatten(self) -> Option { + self } } diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index b3b5017e00..5b815acc89 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -8,9 +8,9 @@ use crate::{ MouseButton, MouseMovedEvent, PromptLevel, WindowBounds, }, scene::{ - CursorRegion, InteractiveRegion, MouseClick, MouseClickOut, MouseDown, MouseDownOut, - MouseDrag, MouseEvent, MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, - MouseUpOut, Scene, + CursorRegion, EventHandler, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag, + MouseEvent, MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, + Scene, }, text_layout::TextLayoutCache, util::post_inc, @@ -57,7 +57,7 @@ pub struct Window { appearance: Appearance, cursor_regions: Vec, mouse_regions: Vec<(MouseRegion, usize)>, - interactive_regions: Vec, + event_handlers: Vec, last_mouse_moved_event: Option, pub(crate) hovered_region_ids: Vec, pub(crate) clicked_region_ids: Vec, @@ -91,7 +91,7 @@ impl Window { rendered_views: Default::default(), cursor_regions: Default::default(), mouse_regions: Default::default(), - interactive_regions: Vec::new(), + event_handlers: Default::default(), text_layout_cache: TextLayoutCache::new(cx.font_system.clone()), last_mouse_moved_event: None, hovered_region_ids: Default::default(), @@ -119,8 +119,8 @@ impl Window { .expect("root_view called during window construction") } - pub fn take_interactive_regions(&mut self) -> Vec { - mem::take(&mut self.interactive_regions) + pub fn take_event_handlers(&mut self) -> Vec { + mem::take(&mut self.event_handlers) } } @@ -889,26 +889,13 @@ impl<'a> WindowContext<'a> { fn dispatch_to_interactive_regions(&mut self, event: &Event) { if let Some(mouse_event) = event.mouse_event() { let mouse_position = event.position().expect("mouse events must have a position"); - let interactive_regions = self.window.take_interactive_regions(); - - for region in interactive_regions.iter().rev() { - if region.event_type == mouse_event.type_id() { - let in_bounds = region.bounds.contains_point(mouse_position); - - if in_bounds == !region.outside_bounds { - self.update_any_view(region.view_id, |view, window_cx| { - (region.event_handler)( - view.as_any_mut(), - mouse_event, - window_cx, - region.view_id, - ); - }); - } + let event_handlers = self.window.take_event_handlers(); + for event_handler in event_handlers.iter().rev() { + if event_handler.event_type == mouse_event.type_id() { + (event_handler.handler)(mouse_event, self); } } - - self.window.interactive_regions = interactive_regions; + self.window.event_handlers = event_handlers; } } @@ -1066,7 +1053,7 @@ impl<'a> WindowContext<'a> { let mut scene = scene_builder.build(); self.window.cursor_regions = scene.cursor_regions(); self.window.mouse_regions = scene.mouse_regions(); - self.window.interactive_regions = scene.take_interactive_regions(); + self.window.event_handlers = scene.take_event_handlers(); if self.window_is_active() { if let Some(event) = self.window.last_mouse_moved_event.clone() { diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 830ffa857e..9f326c1708 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -31,8 +31,6 @@ pub struct SceneBuilder { scale_factor: f32, stacking_contexts: Vec, active_stacking_context_stack: Vec, - /// Used by the playground crate. I hope to replace it with event_handlers. - pub interactive_regions: Vec, /// Used by the playground crate. pub event_handlers: Vec, #[cfg(debug_assertions)] @@ -42,7 +40,6 @@ pub struct SceneBuilder { pub struct Scene { scale_factor: f32, stacking_contexts: Vec, - interactive_regions: Vec, event_handlers: Vec, } @@ -285,15 +282,9 @@ impl Scene { .collect() } - /// TODO: Hoping to replace this with take_event_handlers - pub fn take_interactive_regions(&mut self) -> Vec { - self.interactive_regions - .sort_by(|a, b| a.order.cmp(&b.order)); - std::mem::take(&mut self.interactive_regions) - } - pub fn take_event_handlers(&mut self) -> Vec { - self.event_handlers.sort_by(|a, b| a.order.cmp(&b.order)); + self.event_handlers + .sort_by(|a, b| a.order.cmp(&b.order).reverse()); std::mem::take(&mut self.event_handlers) } } @@ -307,7 +298,6 @@ impl SceneBuilder { active_stacking_context_stack: vec![0], #[cfg(debug_assertions)] mouse_region_ids: Default::default(), - interactive_regions: Vec::new(), event_handlers: Vec::new(), } } @@ -318,7 +308,6 @@ impl SceneBuilder { Scene { scale_factor: self.scale_factor, stacking_contexts: self.stacking_contexts, - interactive_regions: self.interactive_regions, event_handlers: self.event_handlers, } } @@ -716,23 +705,10 @@ impl MouseRegion { } } -/// This is currently only used in the playground crate. It represents a region -/// with which the user can interact via a pointing device. It aims to replace -/// MouseRegion and CursorRegion. -pub struct InteractiveRegion { - pub order: u32, - pub bounds: RectF, - pub outside_bounds: bool, - pub event_handler: Rc, - pub event_type: TypeId, - pub view_id: usize, -} - pub struct EventHandler { pub order: u32, - // First param is a dynamic view reference - // Second param is a dynamic event reference - pub handler: Rc, + // The &dyn Any parameter below expects an event. + pub handler: Rc bool>, pub event_type: TypeId, }