diff --git a/crates/gpui/playground/src/div.rs b/crates/gpui/playground/src/div.rs index a9bf89dc2a..75ac5d965b 100644 --- a/crates/gpui/playground/src/div.rs +++ b/crates/gpui/playground/src/div.rs @@ -25,9 +25,13 @@ pub fn div() -> Div { } impl Element for Div { - type Layout = (); + type PaintState = (); - fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result> + fn layout( + &mut self, + view: &mut V, + cx: &mut LayoutContext, + ) -> Result<(LayoutId, Self::PaintState)> where Self: Sized, { @@ -47,14 +51,16 @@ impl Element for Div { cx.pop_text_style(); } - let layout = cx.add_layout_node(style, (), children.clone())?; - - dbg!(layout.id(), children); - Ok(layout) + Ok((cx.add_layout_node(style, children)?, ())) } - fn paint(&mut self, view: &mut V, layout: &mut Layout, cx: &mut PaintContext) - where + fn paint( + &mut self, + view: &mut V, + layout: &Layout, + paint_state: &mut Self::PaintState, + cx: &mut PaintContext, + ) where Self: Sized, { let style = &self.computed_style(); @@ -62,9 +68,9 @@ impl Element for Div { cx.push_text_style(cx.text_style().clone().refined(&style)); true }); - style.paint_background(layout.bounds(cx), cx); + style.paint_background(layout.bounds, cx); self.interaction_handlers() - .paint(layout.order(cx), layout.bounds(cx), cx); + .paint(layout.order, layout.bounds, cx); for child in &mut self.children { child.paint(view, cx); } diff --git a/crates/gpui/playground/src/element.rs b/crates/gpui/playground/src/element.rs index 3de6ea2903..c39ee7e196 100644 --- a/crates/gpui/playground/src/element.rs +++ b/crates/gpui/playground/src/element.rs @@ -1,29 +1,25 @@ -use anyhow::Result; -use gpui::{geometry::rect::RectF, EngineLayout}; -use smallvec::SmallVec; -use std::marker::PhantomData; -use util::ResultExt; - pub use crate::layout_context::LayoutContext; pub use crate::paint_context::PaintContext; - -type LayoutId = gpui::LayoutId; +use anyhow::Result; +pub use gpui::{Layout, LayoutId}; +use smallvec::SmallVec; pub trait Element: 'static { - type Layout; + type PaintState; fn layout( &mut self, view: &mut V, cx: &mut LayoutContext, - ) -> Result> + ) -> Result<(LayoutId, Self::PaintState)> where Self: Sized; fn paint( &mut self, view: &mut V, - layout: &mut Layout, + layout: &Layout, + state: &mut Self::PaintState, cx: &mut PaintContext, ) where Self: Sized; @@ -34,7 +30,7 @@ pub trait Element: 'static { { AnyElement(Box::new(StatefulElement { element: self, - layout: None, + phase: ElementPhase::Init, })) } } @@ -48,24 +44,71 @@ trait AnyStatefulElement { /// A wrapper around an element that stores its layout state. struct StatefulElement> { element: E, - layout: Option>, + phase: ElementPhase, +} + +enum ElementPhase> { + Init, + PostLayout { + layout_id: LayoutId, + paint_state: E::PaintState, + }, + PostPaint { + layout: Layout, + paint_state: E::PaintState, + }, + Error(String), +} + +impl> Default for ElementPhase { + fn default() -> Self { + Self::Init + } } /// We blanket-implement the object-safe ElementStateObject interface to make ElementStates into trait objects impl> AnyStatefulElement for StatefulElement { 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) + let result; + self.phase = match std::mem::take(&mut self.phase) { + ElementPhase::Init => match self.element.layout(view, cx) { + Ok((layout_id, paint_state)) => { + result = Ok(layout_id); + ElementPhase::PostLayout { + layout_id, + paint_state, + } + } + Err(error) => { + let message = error.to_string(); + result = Err(error); + ElementPhase::Error(message) + } + }, + _ => panic!("invalid element phase to call layout"), + }; + + result } 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 = dbg!(cx.computed_layout(dbg!(layout.id)).log_err()) - } - self.element.paint(view, layout, cx) + self.phase = match std::mem::take(&mut self.phase) { + ElementPhase::PostLayout { + layout_id, + mut paint_state, + } => match cx.computed_layout(layout_id) { + Ok(layout) => { + self.element.paint(view, &layout, &mut paint_state, cx); + ElementPhase::PostPaint { + layout, + paint_state, + } + } + Err(error) => ElementPhase::Error(error.to_string()), + }, + phase @ ElementPhase::Error(_) => phase, + _ => panic!("invalid element phase to call paint"), + }; } } @@ -82,55 +125,6 @@ impl AnyElement { } } -pub struct Layout { - id: LayoutId, - engine_layout: Option, - element_data: Option, - view_type: PhantomData, -} - -impl Layout { - pub fn new(id: LayoutId, element_data: D) -> Self { - Self { - id, - engine_layout: None, - element_data: Some(element_data), - view_type: PhantomData, - } - } - - pub fn id(&self) -> LayoutId { - self.id - } - - pub fn bounds(&mut self, cx: &mut PaintContext) -> RectF { - self.engine_layout(cx).bounds - } - - pub fn order(&mut self, cx: &mut PaintContext) -> u32 { - self.engine_layout(cx).order - } - - pub fn update(&mut self, update: F) -> T - where - F: FnOnce(&mut Self, &mut D) -> T, - { - self.element_data - .take() - .map(|mut element_data| { - let result = update(self, &mut element_data); - self.element_data = Some(element_data); - result - }) - .expect("reentrant calls to Layout::update are not supported") - } - - fn engine_layout(&mut self, cx: &mut PaintContext<'_, '_, '_, '_, V>) -> &mut EngineLayout { - self.engine_layout - .get_or_insert_with(|| cx.computed_layout(self.id).log_err().unwrap_or_default()) - } -} - 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 7da8334225..8ed06b7cbe 100644 --- a/crates/gpui/playground/src/hoverable.rs +++ b/crates/gpui/playground/src/hoverable.rs @@ -6,7 +6,7 @@ use crate::{ style::{Style, StyleHelpers, Styleable}, }; use anyhow::Result; -use gpui::platform::MouseMovedEvent; +use gpui::{platform::MouseMovedEvent, LayoutId}; use refineable::{CascadeSlot, Refineable, RefinementCascade}; use smallvec::SmallVec; use std::{cell::Cell, rc::Rc}; @@ -40,40 +40,44 @@ impl Styleable for Hoverable { } impl + Styleable> Element for Hoverable { - type Layout = E::Layout; + type PaintState = E::PaintState; - fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result> + fn layout( + &mut self, + view: &mut V, + cx: &mut LayoutContext, + ) -> Result<(LayoutId, Self::PaintState)> where Self: Sized, { - self.child.layout(view, cx) + Ok(self.child.layout(view, cx)?) } fn paint( &mut self, view: &mut V, - layout: &mut Layout, + layout: &Layout, + paint_state: &mut Self::PaintState, cx: &mut PaintContext, ) where Self: Sized, { - let bounds = layout.bounds(cx); - let order = layout.order(cx); - - self.hovered.set(bounds.contains_point(cx.mouse_position())); + self.hovered + .set(layout.bounds.contains_point(cx.mouse_position())); let slot = self.cascade_slot; let style = self.hovered.get().then_some(self.hovered_style.clone()); self.style_cascade().set(slot, style); let hovered = self.hovered.clone(); - cx.on_event(order, move |view, event: &MouseMovedEvent, cx| { + let bounds = layout.bounds; + cx.on_event(layout.order, move |view, event: &MouseMovedEvent, cx| { if bounds.contains_point(cx.mouse_position()) != hovered.get() { cx.repaint(); } }); - self.child.paint(view, layout, cx); + self.child.paint(view, layout, paint_state, cx); } } diff --git a/crates/gpui/playground/src/layout_context.rs b/crates/gpui/playground/src/layout_context.rs index 71bd3ac5a3..a08efdac34 100644 --- a/crates/gpui/playground/src/layout_context.rs +++ b/crates/gpui/playground/src/layout_context.rs @@ -1,10 +1,9 @@ +use crate::{element::LayoutId, style::Style}; use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; use gpui::{geometry::Size, MeasureParams, RenderContext, ViewContext}; pub use gpui::{taffy::tree::NodeId, LayoutContext as LegacyLayoutContext}; -use crate::{element::Layout, style::Style}; - #[derive(Deref, DerefMut)] pub struct LayoutContext<'a, 'b, 'c, 'd, V> { #[deref] @@ -35,12 +34,11 @@ impl<'a, 'b, 'c, 'd, V: 'static> LayoutContext<'a, 'b, 'c, 'd, V> { Self { legacy_cx } } - pub fn add_layout_node( + pub fn add_layout_node( &mut self, style: Style, - element_data: D, children: impl IntoIterator, - ) -> Result> { + ) -> Result { let rem_size = self.rem_pixels(); let id = self .legacy_cx @@ -48,15 +46,10 @@ impl<'a, 'b, 'c, 'd, V: 'static> LayoutContext<'a, 'b, 'c, 'd, V> { .ok_or_else(|| anyhow!("no layout engine"))? .add_node(style.to_taffy(rem_size), children)?; - Ok(Layout::new(id, element_data)) + Ok(id) } - pub fn add_measured_layout_node( - &mut self, - style: Style, - element_data: D, - measure: F, - ) -> Result> + pub fn add_measured_layout_node(&mut self, style: Style, measure: F) -> Result where F: Fn(MeasureParams) -> Size + Sync + Send + 'static, { @@ -66,6 +59,6 @@ impl<'a, 'b, 'c, 'd, V: 'static> LayoutContext<'a, 'b, 'c, 'd, V> { .ok_or_else(|| anyhow!("no layout engine"))? .add_measured_node(style.to_taffy(rem_size), measure)?; - Ok(Layout::new(layout_id, element_data)) + Ok(layout_id) } } diff --git a/crates/gpui/playground/src/paint_context.rs b/crates/gpui/playground/src/paint_context.rs index d49cbaf6be..9cd59143cf 100644 --- a/crates/gpui/playground/src/paint_context.rs +++ b/crates/gpui/playground/src/paint_context.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; pub use gpui::taffy::tree::NodeId; use gpui::{ - scene::EventHandler, EngineLayout, EventContext, LayoutId, PaintContext as LegacyPaintContext, + scene::EventHandler, EventContext, Layout, LayoutId, PaintContext as LegacyPaintContext, RenderContext, ViewContext, }; use std::{any::TypeId, rc::Rc}; @@ -65,7 +65,7 @@ impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> { }) } - pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result { + pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result { self.layout_engine() .ok_or_else(|| anyhow!("no layout engine present"))? .computed_layout(layout_id) diff --git a/crates/gpui/playground/src/pressable.rs b/crates/gpui/playground/src/pressable.rs index a6d35d65c8..261ec31083 100644 --- a/crates/gpui/playground/src/pressable.rs +++ b/crates/gpui/playground/src/pressable.rs @@ -6,7 +6,7 @@ use crate::{ style::{Style, StyleHelpers, Styleable}, }; use anyhow::Result; -use gpui::platform::MouseButtonEvent; +use gpui::{platform::MouseButtonEvent, LayoutId}; use refineable::{CascadeSlot, Refineable, RefinementCascade}; use smallvec::SmallVec; use std::{cell::Cell, rc::Rc}; @@ -40,9 +40,13 @@ impl Styleable for Pressable { } impl + Styleable> Element for Pressable { - type Layout = E::Layout; + type PaintState = E::PaintState; - fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result> + fn layout( + &mut self, + view: &mut V, + cx: &mut LayoutContext, + ) -> Result<(LayoutId, Self::PaintState)> where Self: Sized, { @@ -52,7 +56,8 @@ impl + Styleable> Element for Pressable { fn paint( &mut self, view: &mut V, - layout: &mut Layout, + layout: &Layout, + paint_state: &mut Self::PaintState, cx: &mut PaintContext, ) where Self: Sized, @@ -61,10 +66,9 @@ impl + Styleable> Element for Pressable { let style = self.pressed.get().then_some(self.pressed_style.clone()); self.style_cascade().set(slot, style); - let bounds = layout.bounds(cx); - let order = layout.order(cx); let pressed = self.pressed.clone(); - cx.on_event(order, move |view, event: &MouseButtonEvent, cx| { + let bounds = layout.bounds; + cx.on_event(layout.order, move |view, event: &MouseButtonEvent, cx| { if event.is_down { if bounds.contains_point(event.position) { pressed.set(true); @@ -76,7 +80,7 @@ impl + Styleable> Element for Pressable { } }); - self.child.paint(view, layout, cx); + self.child.paint(view, layout, paint_state, cx); } } diff --git a/crates/gpui/playground/src/text.rs b/crates/gpui/playground/src/text.rs index 58dcaeb1f2..d43ecb0329 100644 --- a/crates/gpui/playground/src/text.rs +++ b/crates/gpui/playground/src/text.rs @@ -4,7 +4,7 @@ use crate::{ paint_context::PaintContext, }; use anyhow::Result; -use gpui::{geometry::Size, text_layout::LineLayout, RenderContext}; +use gpui::{geometry::Size, text_layout::LineLayout, LayoutId, RenderContext}; use parking_lot::Mutex; use std::sync::Arc; @@ -21,67 +21,71 @@ pub struct Text { } impl Element for Text { - type Layout = Arc>>; + type PaintState = Arc>>; fn layout( &mut self, view: &mut V, cx: &mut LayoutContext, - ) -> Result> { + ) -> Result<(LayoutId, Self::PaintState)> { let rem_size = cx.rem_pixels(); let fonts = cx.platform().fonts(); let text_style = cx.text_style(); let line_height = cx.font_cache().line_height(text_style.font_size); let text = self.text.clone(); - let layout = Arc::new(Mutex::new(None)); + let paint_state = Arc::new(Mutex::new(None)); - cx.add_measured_layout_node(Default::default(), layout.clone(), move |params| { - let line_layout = fonts.layout_line( - text.as_ref(), - text_style.font_size, - &[(text.len(), text_style.to_run())], - ); + let layout_id = cx.add_measured_layout_node(Default::default(), { + let paint_state = paint_state.clone(); + move |params| { + let line_layout = fonts.layout_line( + text.as_ref(), + text_style.font_size, + &[(text.len(), text_style.to_run())], + ); - let size = Size { - width: line_layout.width, - height: line_height, - }; + let size = Size { + width: line_layout.width, + height: line_height, + }; - layout.lock().replace(TextLayout { - line_layout: Arc::new(line_layout), - line_height, - }); + paint_state.lock().replace(TextLayout { + line_layout: Arc::new(line_layout), + line_height, + }); - size - }) + size + } + }); + + Ok((layout_id?, paint_state)) } fn paint<'a>( &mut self, view: &mut V, - layout: &mut Layout, + layout: &Layout, + paint_state: &mut Self::PaintState, cx: &mut PaintContext, ) { - let element_layout = layout.update(|layout, element_data| element_data.clone()); - let line_layout; let line_height; { - let element_layout = element_layout.lock(); - let element_layout = element_layout + let paint_state = paint_state.lock(); + let paint_state = paint_state .as_ref() .expect("measurement has not been performed"); - line_layout = element_layout.line_layout.clone(); - line_height = element_layout.line_height; + line_layout = paint_state.line_layout.clone(); + line_height = paint_state.line_height; } let text_style = cx.text_style(); let line = gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]); - let origin = layout.bounds(cx).origin(); + let origin = layout.bounds.origin(); // TODO: We haven't added visible bounds to the new element system yet, so this is a placeholder. - let visible_bounds = layout.bounds(cx); + let visible_bounds = layout.bounds; line.paint(cx.scene, origin, visible_bounds, line_height, cx.legacy_cx); } } diff --git a/crates/gpui/playground_macros/src/derive_element.rs b/crates/gpui/playground_macros/src/derive_element.rs index 66d39d68e6..482f6ccaf4 100644 --- a/crates/gpui/playground_macros/src/derive_element.rs +++ b/crates/gpui/playground_macros/src/derive_element.rs @@ -62,25 +62,26 @@ pub fn derive_element(input: TokenStream) -> TokenStream { impl #impl_generics playground::element::Element<#view_type_name> for #type_name #type_generics #where_clause { - type Layout = playground::element::AnyElement<#view_type_name #lifetimes>; + type PaintState = playground::element::AnyElement<#view_type_name #lifetimes>; fn layout( &mut self, view: &mut V, cx: &mut playground::element::LayoutContext, - ) -> anyhow::Result> { + ) -> anyhow::Result<(playground::element::LayoutId, Self::PaintState)> { let mut rendered_element = self.render(view, cx).into_any(); let layout_id = rendered_element.layout(view, cx)?; - Ok(playground::element::Layout::new(layout_id, rendered_element)) + Ok((layout_id, rendered_element)) } fn paint( &mut self, view: &mut V, - layout: &mut playground::element::Layout, + layout: &playground::element::Layout, + rendered_element: &mut Self::PaintState, cx: &mut playground::element::PaintContext, ) { - layout.update(|_, rendered_element| rendered_element.paint(view, cx)); + rendered_element.paint(view, cx); } } diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index a372b865fe..c243b4591a 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -1304,8 +1304,8 @@ impl LayoutEngine { Ok(()) } - pub fn computed_layout(&mut self, node: LayoutId) -> Result { - Ok(EngineLayout::from(self.0.layout(node)?)) + pub fn computed_layout(&mut self, node: LayoutId) -> Result { + Ok(Layout::from(self.0.layout(node)?)) } } @@ -1329,7 +1329,7 @@ where } #[derive(Debug, Clone, Default)] -pub struct EngineLayout { +pub struct Layout { pub bounds: RectF, pub order: u32, } @@ -1365,7 +1365,7 @@ impl From for AvailableSpace { } } -impl From<&taffy::tree::Layout> for EngineLayout { +impl From<&taffy::tree::Layout> for Layout { fn from(value: &taffy::tree::Layout) -> Self { Self { bounds: RectF::new( diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 66e6648b0e..a0e7804a14 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -7,8 +7,8 @@ pub use assets::*; pub mod elements; pub mod font_cache; mod image_data; -pub use taffy; pub use crate::image_data::ImageData; +pub use taffy; pub mod views; pub use font_cache::FontCache; mod clipboard; @@ -29,8 +29,7 @@ pub mod keymap_matcher; pub mod platform; pub use gpui_macros::{test, Element}; pub use window::{ - Axis, EngineLayout, LayoutEngine, LayoutId, RectFExt, SizeConstraint, Vector2FExt, - WindowContext, + Axis, Layout, LayoutEngine, LayoutId, RectFExt, SizeConstraint, Vector2FExt, WindowContext, }; pub use anyhow;