From 305b9db75999277e439146aab612b10ef0525677 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 11 Mar 2026 14:03:25 -0700 Subject: [PATCH] Add EntityView trait, tighten view.rs docs - EntityView: View-based replacement for Render on entity state types. Entity gets a blanket View impl with reactive boundary. - Use ViewElement::new(entity) at call sites for IntoElement. - Add #[inline] on ComponentView and EntityView bridge methods. - Tighten all public docs in view.rs to be short and consistent. - Update ExampleEditor to use EntityView, remove ExampleEditorView wrapper. - ExampleEditorText reads text_color from inherited text style. --- crates/gpui/src/view.rs | 153 +++++++++------------------------------- 1 file changed, 33 insertions(+), 120 deletions(-) diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index c093fe31efef8d6c0aae51e3034a6b65932ec1cf..04fbb2465357572f74d99b12d77cb7b23cbe780e 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -25,7 +25,7 @@ struct ViewCacheKey { text_style: TextStyle, } -/// A dynamically-typed handle to a view, which can be downcast to a [Entity] for a specific type. +/// A dynamically-typed view handle that can be downcast to a specific `Entity`. #[derive(Clone, Debug)] pub struct AnyView { entity: AnyEntity, @@ -44,15 +44,13 @@ impl From> for AnyView { } impl AnyView { - /// Indicate that this view should be cached when using it as an element. - /// When using this method, the view's previous layout and paint will be recycled from the previous frame if [Context::notify] has not been called since it was rendered. - /// The one exception is when [Window::refresh] is called, in which case caching is ignored. + /// Enable caching with the given outer layout style. pub fn cached(mut self, style: StyleRefinement) -> Self { self.cached_style = Some(style.into()); self } - /// Convert this to a weak handle. + /// Downgrade to a weak handle. pub fn downgrade(&self) -> AnyWeakView { AnyWeakView { entity: self.entity.downgrade(), @@ -60,8 +58,7 @@ impl AnyView { } } - /// Convert this to a [Entity] of a specific type. - /// If this handle does not contain a view of the specified type, returns itself in an `Err` variant. + /// Downcast to a typed `Entity`, returning `Err(self)` on type mismatch. pub fn downcast(self) -> Result, Self> { match self.entity.downcast() { Ok(entity) => Ok(entity), @@ -73,12 +70,12 @@ impl AnyView { } } - /// Gets the [TypeId] of the underlying view. + /// The [`TypeId`] of the underlying view. pub fn entity_type(&self) -> TypeId { self.entity.entity_type } - /// Gets the entity id of this handle. + /// The [`EntityId`] of this view. pub fn entity_id(&self) -> EntityId { self.entity.entity_id() } @@ -256,14 +253,14 @@ impl IntoElement for AnyView { } } -/// A weak, dynamically-typed view handle that does not prevent the view from being released. +/// A weak, dynamically-typed view handle. pub struct AnyWeakView { entity: AnyWeakEntity, render: fn(&AnyView, &mut Window, &mut App) -> AnyElement, } impl AnyWeakView { - /// Convert to a strongly-typed handle if the referenced view has not yet been released. + /// Upgrade to a strong `AnyView` handle, if the view is still alive. pub fn upgrade(&self) -> Option { let entity = self.entity.upgrade()?; Some(AnyView { @@ -310,108 +307,37 @@ mod any_view { } } -/// A component backed by an entity that participates in GPUI's reactive graph. +/// A renderable component that participates in GPUI's reactive graph. /// -/// Views combine the ergonomic builder-pattern API of components ([`RenderOnce`](crate::RenderOnce)) -/// with the push-pull reactivity of entities. When a view's entity calls -/// `cx.notify()`, only this view (and its dirty ancestors/children) need to -/// re-render. -/// -/// Unlike [`Render`], which puts the rendering trait on the entity's state type, -/// `View` goes on the *component* type — the struct that holds both the entity -/// handle and any display props. This means the consumer controls styling -/// through the builder pattern, while the entity provides reactive state. -/// -/// # Example -/// -/// ```ignore -/// struct CounterState { -/// count: usize, -/// } -/// -/// struct Counter { -/// state: Entity, -/// label: SharedString, -/// } -/// -/// impl Counter { -/// fn new(state: Entity) -> Self { -/// Self { state, label: "Count".into() } -/// } -/// -/// fn label(mut self, label: impl Into) -> Self { -/// self.label = label.into(); -/// self -/// } -/// } -/// -/// impl View for Counter { -/// type Entity = CounterState; -/// -/// fn entity(&self) -> Option> { -/// Some(self.state.clone()) -/// } -/// -/// -/// fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { -/// let count = self.state.read(cx).count; -/// div().child(format!("{}: {}", self.label, count)) -/// } -/// } -/// -/// // Usage in a parent's render: -/// // Counter::new(my_counter_entity).label("Total") -/// ``` +/// When `entity()` returns `Some`, `cx.notify()` on that entity only +/// re-renders this view's subtree. Implement this trait directly for +/// full control, or use [`ComponentView`] / [`EntityView`] for the +/// common stateless / stateful cases. pub trait View: 'static + Sized + Hash { /// The entity type that backs this view's state. type Entity: 'static; - /// Returns the entity that backs this view, if any. - /// - /// When `Some`, the view creates a reactive boundary in the element tree — - /// `cx.notify()` on the entity only re-renders this view's subtree. - /// - /// When `None`, the view behaves like a stateless component with subtree - /// isolation via its type name (similar to [`RenderOnce`](crate::RenderOnce)). + /// The entity that backs this view, if any. `Some` creates a reactive boundary; + /// `None` behaves like a stateless component. fn entity(&self) -> Option>; - /// Render this view into an element tree. Takes ownership of self, - /// consuming the component props. The entity state persists across frames. + /// Render this view into an element tree, consuming `self`. fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement; - /// Returns the style to use for caching this view. - /// When `Some`, the view element will be cached using the given style for its outer layout. - /// The default returns a full-size style refinement (`width: 100%, height: 100%`). - /// Return `None` to disable caching. + /// Outer layout style for caching. `Some` enables caching; `None` disables it. + /// Defaults to full-size (`width: 100%, height: 100%`). fn cache_style(&mut self, _window: &mut Window, _cx: &mut App) -> Option { Some(StyleRefinement::default().size_full()) } } /// A stateless component that renders an element tree without an entity. -/// -/// This is the `View` equivalent of [`RenderOnce`](crate::RenderOnce). -/// -/// # Example -/// -/// ```ignore -/// #[derive(Hash, IntoViewElement)] -/// struct Greeting { -/// name: SharedString, -/// } -/// -/// impl ComponentView for Greeting { -/// fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { -/// div().child(format!("Hello, {}!", self.name)) -/// } -/// } -/// ``` +/// This is the [`View`] equivalent of [`RenderOnce`](crate::RenderOnce). pub trait ComponentView: 'static + Sized + Hash { - /// Render this component into an element tree. Takes ownership of self, - /// consuming the component props. + /// Render this component into an element tree, consuming `self`. fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement; - /// Indicate that this view should be cached + /// Outer layout style for caching. Defaults to `None` (no caching). fn cache_style(&mut self, _window: &mut Window, _cx: &mut App) -> Option { None } @@ -435,13 +361,16 @@ impl View for T { } } -/// For entities that require only one kind of rendering, this trait provides a simplified interface. -/// Equivalent to the `Render` trait +/// A stateful entity that renders an element tree with a reactive boundary. +/// This is the [`View`]-based replacement for [`Render`]. +/// +/// Gives `Entity` a blanket [`View`] impl. Use `ViewElement::new(entity)` +/// to convert an `Entity` into a child element. pub trait EntityView: 'static + Sized { - /// Render this entity into the element tree. + /// Render this entity into an element tree. fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement; - /// Indicate that this entity should be cached when using it as an element. - /// When using this method, the entity's previous layout and paint will be recycled from the previous frame if [Context::notify] has not been called since it was rendered. + + /// Outer layout style for caching. Defaults to `None` (no caching). fn cache_style( &mut self, _window: &mut Window, @@ -471,23 +400,8 @@ impl View for Entity { } } -/// An element that wraps a [`View`], creating a reactive boundary in the element tree. -/// -/// This is the stateful counterpart to [`Component`](crate::Component) — where `Component` -/// wraps a stateless [`RenderOnce`](crate::RenderOnce) type, `ViewElement` wraps a stateful -/// [`View`] type and hooks its entity into GPUI's push-pull reactive graph. -/// -/// You don't construct this directly. Instead, implement [`IntoElement`] for your -/// [`View`] type using [`ViewElement::new`]: -/// -/// ```ignore -/// impl IntoElement for Counter { -/// type Element = ViewElement; -/// fn into_element(self) -> Self::Element { -/// ViewElement::new(self) -/// } -/// } -/// ``` +/// The element type for [`View`] implementations. Wraps a `View` and hooks +/// it into. Constructed via [`ViewElement::new`]. #[doc(hidden)] pub struct ViewElement { view: Option, @@ -499,9 +413,8 @@ pub struct ViewElement { } impl ViewElement { - /// Create a new `ViewElement` wrapping the given [`View`]. - /// - /// Use this in your [`IntoElement`] implementation. + /// Wrap a [`View`] as an element. + #[track_caller] pub fn new(view: V) -> Self { use std::hash::Hasher; let entity_id = view.entity().map(|e| e.entity_id());