Add EntityView trait, tighten view.rs docs

Mikayla Maki created

- EntityView: View-based replacement for Render on entity state types.
  Entity<T: EntityView> 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.

Change summary

crates/gpui/src/view.rs | 153 +++++++++---------------------------------
1 file changed, 33 insertions(+), 120 deletions(-)

Detailed changes

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<V>`.
 #[derive(Clone, Debug)]
 pub struct AnyView {
     entity: AnyEntity,
@@ -44,15 +44,13 @@ impl<V: Render> From<Entity<V>> 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<T>`, returning `Err(self)` on type mismatch.
     pub fn downcast<T: 'static>(self) -> Result<Entity<T>, 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<AnyView> {
         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<CounterState>,
-///     label: SharedString,
-/// }
-///
-/// impl Counter {
-///     fn new(state: Entity<CounterState>) -> Self {
-///         Self { state, label: "Count".into() }
-///     }
-///
-///     fn label(mut self, label: impl Into<SharedString>) -> Self {
-///         self.label = label.into();
-///         self
-///     }
-/// }
-///
-/// impl View for Counter {
-///     type Entity = CounterState;
-///
-///     fn entity(&self) -> Option<Entity<CounterState>> {
-///         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<Entity<Self::Entity>>;
 
-    /// 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<StyleRefinement> {
         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<StyleRefinement> {
         None
     }
@@ -435,13 +361,16 @@ impl<T: ComponentView> 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<T>` a blanket [`View`] impl. Use `ViewElement::new(entity)`
+/// to convert an `Entity<T>` 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<Self>) -> 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<T: EntityView> View for Entity<T> {
     }
 }
 
-/// 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<C>`
-/// wraps a stateless [`RenderOnce`](crate::RenderOnce) type, `ViewElement<V>` 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<Self>;
-///     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<V: View> {
     view: Option<V>,
@@ -499,9 +413,8 @@ pub struct ViewElement<V: View> {
 }
 
 impl<V: View> ViewElement<V> {
-    /// 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());