use crate::{ AnyElement, AnyEntity, AnyWeakEntity, App, Bounds, ContentMask, Context, Element, ElementId, Entity, EntityId, GlobalElementId, InspectorElementId, IntoElement, LayoutId, PaintIndex, Pixels, PrepaintStateIndex, Render, RenderOnce, Style, StyleRefinement, TextStyle, WeakEntity, }; use crate::{Empty, Window}; use anyhow::Result; use collections::FxHashSet; use refineable::Refineable; use std::mem; use std::{any::TypeId, fmt, ops::Range}; /// A dynamically-typed view handle that can be downcast to a specific `Entity`. /// /// This is the type-erased counterpart to [`ViewElement`]: it holds an entity plus /// a function pointer to its render, and is itself a [`View`], so embedding it as an /// element goes through the same [`ViewElement`] machinery as any other view. #[derive(Clone, Debug)] pub struct AnyView { entity: AnyEntity, render: fn(&AnyView, &mut Window, &mut App) -> AnyElement, } impl From> for AnyView { fn from(value: Entity) -> Self { AnyView { entity: value.into_any(), render: any_view::render::, } } } impl AnyView { /// Embed this view as a cached [`ViewElement`] laid out at `style`. /// /// The rendered subtree is recycled from the previous frame unless /// [Context::notify] was called on the backing entity since it was rendered /// (or [Window::refresh] is called, which ignores caching). pub fn cached(self, style: StyleRefinement) -> ViewElement { ViewElement::new(self).cached(style) } /// Convert this to a weak handle. pub fn downgrade(&self) -> AnyWeakView { AnyWeakView { entity: self.entity.downgrade(), render: self.render, } } /// 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. pub fn downcast(self) -> Result, Self> { match self.entity.downcast() { Ok(entity) => Ok(entity), Err(entity) => Err(Self { entity, render: self.render, }), } } /// Gets the [TypeId] of the underlying view. pub fn entity_type(&self) -> TypeId { self.entity.entity_type } /// The [`EntityId`] of this view. pub fn entity_id(&self) -> EntityId { self.entity.entity_id() } } impl PartialEq for AnyView { fn eq(&self, other: &Self) -> bool { self.entity == other.entity } } impl Eq for AnyView {} /// `AnyView` is the type-erased [`View`]: its `render` is a function pointer rather /// than a concrete type, but it participates in the reactive graph exactly like any /// other view via [`ViewElement`]. impl View for AnyView { fn entity_id(&self) -> Option { Some(self.entity.entity_id()) } fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { (self.render)(&self, window, cx) } } impl IntoElement for Entity { type Element = ViewElement>; fn into_element(self) -> Self::Element { ViewElement::new(self) } } impl IntoElement for AnyView { type Element = ViewElement; fn into_element(self) -> Self::Element { ViewElement::new(self) } } /// A weak, dynamically-typed view handle. pub struct AnyWeakView { entity: AnyWeakEntity, render: fn(&AnyView, &mut Window, &mut App) -> AnyElement, } impl AnyWeakView { /// Upgrade to a strong `AnyView` handle, if the view is still alive. pub fn upgrade(&self) -> Option { let entity = self.entity.upgrade()?; Some(AnyView { entity, render: self.render, }) } } impl From> for AnyWeakView { fn from(view: WeakEntity) -> Self { AnyWeakView { entity: view.into(), render: any_view::render::, } } } impl PartialEq for AnyWeakView { fn eq(&self, other: &Self) -> bool { self.entity == other.entity } } impl std::fmt::Debug for AnyWeakView { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("AnyWeakView") .field("entity_id", &self.entity.entity_id) .finish_non_exhaustive() } } mod any_view { use crate::{AnyElement, AnyView, App, IntoElement, Render, Window}; pub(crate) fn render( view: &AnyView, window: &mut Window, cx: &mut App, ) -> AnyElement { let view = view.clone().downcast::().unwrap(); // Record the view's Render type name so the accessibility debug dump can // attribute nodes to the view that produced them. #[cfg(debug_assertions)] window .a11y .view_type_names .insert(view.entity_id(), std::any::type_name::()); view.update(cx, |view, cx| view.render(window, cx).into_any_element()) } } /// A renderable that participates in GPUI's reactive graph — the unifying model /// behind [`Render`] and [`RenderOnce`]. /// /// When `entity_id()` returns `Some`, that id becomes the view's identity: it gets /// a unique element-id space (so internal `use_state` / `.id(..)` never collide /// across siblings) and `cx.notify()` on that entity re-renders only this view's /// subtree. `None` behaves like a stateless component. /// /// You rarely implement `View` directly. `Entity` and any `T: RenderOnce` /// get a blanket impl below; implement it by hand only when a component needs both /// parent-supplied props *and* a backing entity for identity. pub trait View: 'static + Sized { /// This view's identity, if it has one. A view typically holds the backing /// entity as a field and returns its [`EntityId`] here. /// /// The id becomes this view's [`ElementId`], so two views keyed on the same /// entity must not be rendered at the same position in the element tree /// (e.g. as siblings under the same parent): their internal element state /// (`use_state`, scroll offsets, etc.) would silently collide. Nesting is /// fine — the id is scoped by the parent path. fn entity_id(&self) -> Option; /// Render this view into an element tree, consuming `self`. fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement; } /// A stateless component (`RenderOnce`) is a `View` with no identity. impl View for T { fn entity_id(&self) -> Option { None } #[inline] fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { RenderOnce::render(self, window, cx) } } /// An entity that renders itself (`Render`) is a `View` keyed on its own id. impl View for Entity { fn entity_id(&self) -> Option { Some(Entity::entity_id(self)) } #[inline] fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { self.update(cx, |this, cx| { Render::render(this, window, cx).into_any_element() }) } } impl Entity { /// Embed this entity as a cached [`ViewElement`] laid out at `style`. /// /// The rendered subtree is reused until the entity is notified (or the /// cached bounds / text style change). Caching requires a definite size: /// a cached view is laid out from `style` and is *not* measured from its /// contents. Use [`ViewElement::new`] (or `.child(entity)`) for the /// uncached case. #[track_caller] pub fn cached(self, style: StyleRefinement) -> ViewElement> { ViewElement::new(self).cached(style) } } /// The element type for [`View`] implementations. Wraps a `View` and hooks it /// into layout, prepaint, and paint. Constructed via [`ViewElement::new`]. #[doc(hidden)] pub struct ViewElement { view: Option, entity_id: Option, cached_style: Option, #[cfg(debug_assertions)] source: &'static core::panic::Location<'static>, } impl ViewElement { /// Wrap a [`View`] as an element. #[track_caller] pub fn new(view: V) -> Self { let entity_id = view.entity_id(); ViewElement { entity_id, cached_style: None, view: Some(view), #[cfg(debug_assertions)] source: core::panic::Location::caller(), } } /// Enable caching of this view's rendered subtree, laid out at `style`. /// The composer supplies the layout style because caching skips rendering /// the contents to measure them. /// /// Crate-private on purpose: caching is only sound for entity-backed views, /// where [`Context::notify`] is the contract that busts the cache. A stateless /// view has no such contract, so a frozen subtree could never be invalidated. /// Reach this through [`Entity::cached`] or [`AnyView::cached`], which are /// entity-backed by construction. pub(crate) fn cached(mut self, style: StyleRefinement) -> Self { self.cached_style = Some(style); self } } impl IntoElement for ViewElement { type Element = Self; fn into_element(self) -> Self::Element { self } } struct ViewElementState { prepaint_range: Range, paint_range: Range, cache_key: ViewElementCacheKey, accessed_entities: FxHashSet, } struct ViewElementCacheKey { bounds: Bounds, content_mask: ContentMask, text_style: TextStyle, } impl Element for ViewElement { type RequestLayoutState = Option; type PrepaintState = Option; fn id(&self) -> Option { self.entity_id.map(ElementId::View) } fn source_location(&self) -> Option<&'static core::panic::Location<'static>> { #[cfg(debug_assertions)] return Some(self.source); #[cfg(not(debug_assertions))] return None; } fn request_layout( &mut self, _id: Option<&GlobalElementId>, _inspector_id: Option<&InspectorElementId>, window: &mut Window, cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { if let Some(entity_id) = self.entity_id { // Stateful path: create a reactive boundary. window.with_rendered_view(entity_id, |window| { let caching_disabled = window.is_inspector_picking(cx); match self.cached_style.as_ref() { Some(style) if !caching_disabled => { let mut root_style = Style::default(); root_style.refine(style); let layout_id = window.request_layout(root_style, None, cx); (layout_id, None) } _ => { let mut element = self .view .take() .unwrap() .render(window, cx) .into_any_element(); let layout_id = element.request_layout(window, cx); (layout_id, Some(element)) } } }) } else { // Stateless path: isolate subtree via type name (no entity identity). window.with_id( ElementId::Name(std::any::type_name::().into()), |window| { let mut element = self .view .take() .unwrap() .render(window, cx) .into_any_element(); let layout_id = element.request_layout(window, cx); (layout_id, Some(element)) }, ) } } fn prepaint( &mut self, global_id: Option<&GlobalElementId>, _inspector_id: Option<&InspectorElementId>, bounds: Bounds, element: &mut Self::RequestLayoutState, window: &mut Window, cx: &mut App, ) -> Option { if let Some(entity_id) = self.entity_id { // Stateful path. window.set_view_id(entity_id); window.with_rendered_view(entity_id, |window| { if let Some(mut element) = element.take() { element.prepaint(window, cx); return Some(element); } window.with_element_state::( global_id.unwrap(), |element_state, window| { let content_mask = window.content_mask(); let text_style = window.text_style(); if let Some(mut element_state) = element_state && element_state.cache_key.bounds == bounds && element_state.cache_key.content_mask == content_mask && element_state.cache_key.text_style == text_style && !window.dirty_views.contains(&entity_id) && !window.refreshing { let prepaint_start = window.prepaint_index(); window.reuse_prepaint(element_state.prepaint_range.clone()); cx.entities .extend_accessed(&element_state.accessed_entities); let prepaint_end = window.prepaint_index(); element_state.prepaint_range = prepaint_start..prepaint_end; return (None, element_state); } let refreshing = mem::replace(&mut window.refreshing, true); let prepaint_start = window.prepaint_index(); let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| { let mut element = self .view .take() .unwrap() .render(window, cx) .into_any_element(); element.layout_as_root(bounds.size.into(), window, cx); element.prepaint_at(bounds.origin, window, cx); element }); let prepaint_end = window.prepaint_index(); window.refreshing = refreshing; ( Some(element), ViewElementState { accessed_entities, prepaint_range: prepaint_start..prepaint_end, paint_range: PaintIndex::default()..PaintIndex::default(), cache_key: ViewElementCacheKey { bounds, content_mask, text_style, }, }, ) }, ) }) } else { // Stateless path: just prepaint the element. window.with_id( ElementId::Name(std::any::type_name::().into()), |window| { element.as_mut().unwrap().prepaint(window, cx); }, ); Some(element.take().unwrap()) } } fn paint( &mut self, global_id: Option<&GlobalElementId>, _inspector_id: Option<&InspectorElementId>, _bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, element: &mut Self::PrepaintState, window: &mut Window, cx: &mut App, ) { if let Some(entity_id) = self.entity_id { // Stateful path. window.with_rendered_view(entity_id, |window| { let caching_disabled = window.is_inspector_picking(cx); if self.cached_style.is_some() && !caching_disabled { window.with_element_state::( global_id.unwrap(), |element_state, window| { let mut element_state = element_state.unwrap(); let paint_start = window.paint_index(); if let Some(element) = element { let refreshing = mem::replace(&mut window.refreshing, true); element.paint(window, cx); window.refreshing = refreshing; } else { window.reuse_paint(element_state.paint_range.clone()); } let paint_end = window.paint_index(); element_state.paint_range = paint_start..paint_end; ((), element_state) }, ) } else { element.as_mut().unwrap().paint(window, cx); } }); } else { // Stateless path: just paint the element. window.with_id( ElementId::Name(std::any::type_name::().into()), |window| { element.as_mut().unwrap().paint(window, cx); }, ); } } } /// A view that renders nothing pub struct EmptyView; impl Render for EmptyView { fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { Empty } }