Skip to main content

gpui/
view.rs

1use crate::{
2    AnyElement, AnyEntity, AnyWeakEntity, App, AvailableSpace, Bounds, ContentMask, Context,
3    Element, ElementId, Entity, EntityId, GlobalElementId, InspectorElementId, IntoElement,
4    LayoutId, PaintIndex, Pixels, PrepaintStateIndex, Render, RenderOnce, Size, Style,
5    StyleRefinement, TextStyle, WeakEntity,
6};
7use crate::{Empty, Window};
8use anyhow::Result;
9use collections::FxHashSet;
10use refineable::Refineable;
11use std::mem;
12use std::{
13    any::{TypeId, type_name},
14    fmt,
15    ops::Range,
16};
17
18/// A dynamically-typed view handle that can be downcast to a specific `Entity<V>`.
19///
20/// This is the type-erased counterpart to [`ViewElement`]: it holds an entity plus
21/// a function pointer to its render, and is itself a [`View`], so embedding it as an
22/// element goes through the same [`ViewElement`] machinery as any other view.
23#[derive(Clone, Debug)]
24pub struct AnyView {
25    entity: AnyEntity,
26    render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
27}
28
29impl<V: Render> From<Entity<V>> for AnyView {
30    fn from(value: Entity<V>) -> Self {
31        AnyView {
32            entity: value.into_any(),
33            render: any_view::render::<V>,
34        }
35    }
36}
37
38impl AnyView {
39    /// Embed this view as a cached [`ViewElement`] laid out at `style`.
40    ///
41    /// The rendered subtree is recycled from the previous frame unless
42    /// [Context::notify] was called on the backing entity since it was rendered
43    /// (or [Window::refresh] is called, which ignores caching).
44    pub fn cached(self, style: StyleRefinement) -> ViewElement<AnyView> {
45        ViewElement::new(self).cached(style)
46    }
47
48    /// Convert this to a weak handle.
49    pub fn downgrade(&self) -> AnyWeakView {
50        AnyWeakView {
51            entity: self.entity.downgrade(),
52            render: self.render,
53        }
54    }
55
56    /// Convert this to a [Entity] of a specific type.
57    /// If this handle does not contain a view of the specified type, returns itself in an `Err` variant.
58    pub fn downcast<T: 'static>(self) -> Result<Entity<T>, Self> {
59        match self.entity.downcast() {
60            Ok(entity) => Ok(entity),
61            Err(entity) => Err(Self {
62                entity,
63                render: self.render,
64            }),
65        }
66    }
67
68    /// Gets the [TypeId] of the underlying view.
69    pub fn entity_type(&self) -> TypeId {
70        self.entity.entity_type
71    }
72
73    /// The [`EntityId`] of this view.
74    pub fn entity_id(&self) -> EntityId {
75        self.entity.entity_id()
76    }
77}
78
79impl PartialEq for AnyView {
80    fn eq(&self, other: &Self) -> bool {
81        self.entity == other.entity
82    }
83}
84
85impl Eq for AnyView {}
86
87/// `AnyView` is the type-erased [`View`]: its `render` is a function pointer rather
88/// than a concrete type, but it participates in the reactive graph exactly like any
89/// other view via [`ViewElement`].
90impl View for AnyView {
91    fn entity_id(&self) -> Option<EntityId> {
92        Some(self.entity.entity_id())
93    }
94
95    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
96        (self.render)(&self, window, cx)
97    }
98}
99
100impl<V: 'static + Render> IntoElement for Entity<V> {
101    type Element = ViewElement<Entity<V>>;
102
103    fn into_element(self) -> Self::Element {
104        ViewElement::new(self)
105    }
106
107    #[inline(never)]
108    fn into_any_element(self) -> AnyElement {
109        self.into_element().into_any()
110    }
111}
112
113impl IntoElement for AnyView {
114    type Element = ViewElement<AnyView>;
115
116    fn into_element(self) -> Self::Element {
117        ViewElement::new(self)
118    }
119}
120
121/// A weak, dynamically-typed view handle.
122pub struct AnyWeakView {
123    entity: AnyWeakEntity,
124    render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
125}
126
127impl AnyWeakView {
128    /// Upgrade to a strong `AnyView` handle, if the view is still alive.
129    pub fn upgrade(&self) -> Option<AnyView> {
130        let entity = self.entity.upgrade()?;
131        Some(AnyView {
132            entity,
133            render: self.render,
134        })
135    }
136}
137
138impl<V: 'static + Render> From<WeakEntity<V>> for AnyWeakView {
139    fn from(view: WeakEntity<V>) -> Self {
140        AnyWeakView {
141            entity: view.into(),
142            render: any_view::render::<V>,
143        }
144    }
145}
146
147impl PartialEq for AnyWeakView {
148    fn eq(&self, other: &Self) -> bool {
149        self.entity == other.entity
150    }
151}
152
153impl std::fmt::Debug for AnyWeakView {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        f.debug_struct("AnyWeakView")
156            .field("entity_id", &self.entity.entity_id)
157            .finish_non_exhaustive()
158    }
159}
160
161mod any_view {
162    use crate::{AnyElement, AnyView, App, IntoElement, Render, Window};
163
164    pub(crate) fn render<V: 'static + Render>(
165        view: &AnyView,
166        window: &mut Window,
167        cx: &mut App,
168    ) -> AnyElement {
169        let view = view.clone().downcast::<V>().unwrap();
170        // Record the view's Render type name so the accessibility debug dump can
171        // attribute nodes to the view that produced them.
172        #[cfg(debug_assertions)]
173        window
174            .a11y
175            .view_type_names
176            .insert(view.entity_id(), std::any::type_name::<V>());
177        view.update(cx, |view, cx| view.render(window, cx).into_any_element())
178    }
179}
180
181/// A renderable that participates in GPUI's reactive graph — the unifying model
182/// behind [`Render`] and [`RenderOnce`].
183///
184/// When `entity_id()` returns `Some`, that id becomes the view's identity: it gets
185/// a unique element-id space (so internal `use_state` / `.id(..)` never collide
186/// across siblings) and `cx.notify()` on that entity re-renders only this view's
187/// subtree. `None` behaves like a stateless component.
188///
189/// You rarely implement `View` directly. `Entity<T: Render>` and any `T: RenderOnce`
190/// get a blanket impl below; implement it by hand only when a component needs both
191/// parent-supplied props *and* a backing entity for identity.
192pub trait View: 'static + Sized {
193    /// This view's identity, if it has one. A view typically holds the backing
194    /// entity as a field and returns its [`EntityId`] here.
195    ///
196    /// The id becomes this view's [`ElementId`], so two views keyed on the same
197    /// entity must not be rendered at the same position in the element tree
198    /// (e.g. as siblings under the same parent): their internal element state
199    /// (`use_state`, scroll offsets, etc.) would silently collide. Nesting is
200    /// fine — the id is scoped by the parent path.
201    fn entity_id(&self) -> Option<EntityId>;
202
203    /// Render this view into an element tree, consuming `self`.
204    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement;
205}
206
207/// A stateless component (`RenderOnce`) is a `View` with no identity.
208impl<T: RenderOnce> View for T {
209    fn entity_id(&self) -> Option<EntityId> {
210        None
211    }
212
213    #[inline]
214    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
215        RenderOnce::render(self, window, cx)
216    }
217}
218
219/// An entity that renders itself (`Render`) is a `View` keyed on its own id.
220impl<T: Render> View for Entity<T> {
221    fn entity_id(&self) -> Option<EntityId> {
222        Some(Entity::entity_id(self))
223    }
224
225    #[inline]
226    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
227        self.update(cx, |this, cx| {
228            Render::render(this, window, cx).into_any_element()
229        })
230    }
231}
232
233impl<T: Render> Entity<T> {
234    /// Embed this entity as a cached [`ViewElement`] laid out at `style`.
235    ///
236    /// The rendered subtree is reused until the entity is notified (or the
237    /// cached bounds / text style change). Caching requires a definite size:
238    /// a cached view is laid out from `style` and is *not* measured from its
239    /// contents. Use [`ViewElement::new`] (or `.child(entity)`) for the
240    /// uncached case.
241    #[track_caller]
242    pub fn cached(self, style: StyleRefinement) -> ViewElement<Entity<T>> {
243        ViewElement::new(self).cached(style)
244    }
245}
246
247/// The element type for [`View`] implementations. Wraps a `View` and hooks it
248/// into layout, prepaint, and paint. Constructed via [`ViewElement::new`].
249#[doc(hidden)]
250pub struct ViewElement<V: View> {
251    view: Option<V>,
252    entity_id: Option<EntityId>,
253    cached_style: Option<StyleRefinement>,
254    #[cfg(debug_assertions)]
255    source: &'static core::panic::Location<'static>,
256}
257
258impl<V: View> ViewElement<V> {
259    /// Wrap a [`View`] as an element.
260    #[track_caller]
261    pub fn new(view: V) -> Self {
262        let entity_id = view.entity_id();
263        ViewElement {
264            entity_id,
265            cached_style: None,
266            view: Some(view),
267            #[cfg(debug_assertions)]
268            source: core::panic::Location::caller(),
269        }
270    }
271
272    /// Enable caching of this view's rendered subtree, laid out at `style`.
273    /// The composer supplies the layout style because caching skips rendering
274    /// the contents to measure them.
275    ///
276    /// Crate-private on purpose: caching is only sound for entity-backed views,
277    /// where [`Context::notify`] is the contract that busts the cache. A stateless
278    /// view has no such contract, so a frozen subtree could never be invalidated.
279    /// Reach this through [`Entity::cached`] or [`AnyView::cached`], which are
280    /// entity-backed by construction.
281    pub(crate) fn cached(mut self, style: StyleRefinement) -> Self {
282        self.cached_style = Some(style);
283        self
284    }
285}
286
287impl<V: View> IntoElement for ViewElement<V> {
288    type Element = Self;
289
290    fn into_element(self) -> Self::Element {
291        self
292    }
293}
294
295struct ViewElementState {
296    prepaint_range: Range<PrepaintStateIndex>,
297    paint_range: Range<PaintIndex>,
298    cache_key: ViewElementCacheKey,
299    accessed_entities: FxHashSet<EntityId>,
300}
301
302struct ViewElementCacheKey {
303    bounds: Bounds<Pixels>,
304    content_mask: ContentMask<Pixels>,
305    text_style: TextStyle,
306}
307
308impl<V: View> Element for ViewElement<V> {
309    type RequestLayoutState = Option<AnyElement>;
310    type PrepaintState = Option<AnyElement>;
311
312    fn id(&self) -> Option<ElementId> {
313        self.entity_id.map(ElementId::View)
314    }
315
316    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
317        #[cfg(debug_assertions)]
318        return Some(self.source);
319
320        #[cfg(not(debug_assertions))]
321        return None;
322    }
323
324    fn request_layout(
325        &mut self,
326        _id: Option<&GlobalElementId>,
327        _inspector_id: Option<&InspectorElementId>,
328        window: &mut Window,
329        cx: &mut App,
330    ) -> (LayoutId, Self::RequestLayoutState) {
331        if let Some(entity_id) = self.entity_id {
332            // Stateful path: create a reactive boundary.
333            let view = &mut self.view;
334            request_layout_view(
335                entity_id,
336                self.cached_style.as_ref(),
337                window,
338                cx,
339                &mut |window, cx| view.take().unwrap().render(window, cx).into_any_element(),
340            )
341        } else {
342            // Stateless path: isolate subtree via type name (no entity identity).
343            request_layout_component(type_name::<V>(), window, cx, &mut |window, cx| {
344                self.view
345                    .take()
346                    .unwrap()
347                    .render(window, cx)
348                    .into_any_element()
349            })
350        }
351    }
352
353    fn prepaint(
354        &mut self,
355        global_id: Option<&GlobalElementId>,
356        _inspector_id: Option<&InspectorElementId>,
357        bounds: Bounds<Pixels>,
358        element: &mut Self::RequestLayoutState,
359        window: &mut Window,
360        cx: &mut App,
361    ) -> Option<AnyElement> {
362        if let Some(entity_id) = self.entity_id {
363            // Stateful path.
364            prepaint_view(
365                entity_id,
366                global_id,
367                bounds,
368                element,
369                window,
370                cx,
371                &mut |window, cx| {
372                    self.view
373                        .take()
374                        .unwrap()
375                        .render(window, cx)
376                        .into_any_element()
377                },
378            )
379        } else {
380            // Stateless path: just prepaint the element.
381            prepaint_component(type_name::<V>(), element, window, cx)
382        }
383    }
384
385    fn paint(
386        &mut self,
387        global_id: Option<&GlobalElementId>,
388        _inspector_id: Option<&InspectorElementId>,
389        _bounds: Bounds<Pixels>,
390        _request_layout: &mut Self::RequestLayoutState,
391        element: &mut Self::PrepaintState,
392        window: &mut Window,
393        cx: &mut App,
394    ) {
395        if let Some(entity_id) = self.entity_id {
396            // Stateful path.
397            paint_view(
398                entity_id,
399                self.cached_style.is_some(),
400                global_id,
401                element,
402                window,
403                cx,
404            );
405        } else {
406            // Stateless path: just paint the element.
407            paint_component(std::any::type_name::<V>(), element, window, cx);
408        }
409    }
410}
411
412/// A view that renders nothing
413pub struct EmptyView;
414
415impl Render for EmptyView {
416    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
417        Empty
418    }
419}
420
421#[inline(never)]
422fn request_layout_view(
423    entity_id: EntityId,
424    cached_style: Option<&StyleRefinement>,
425    window: &mut Window,
426    cx: &mut App,
427    render: &mut dyn FnMut(&mut Window, &mut App) -> AnyElement,
428) -> (LayoutId, Option<AnyElement>) {
429    window.with_rendered_view(entity_id, |window| {
430        let caching_disabled = window.is_inspector_picking(cx);
431        match cached_style {
432            Some(style) if !caching_disabled => {
433                let mut root_style = Style::default();
434                root_style.refine(style);
435                let layout_id = window.request_layout(root_style, None, cx);
436                (layout_id, None)
437            }
438            _ => {
439                let mut element = render(window, cx);
440                let layout_id = element.request_layout(window, cx);
441                (layout_id, Some(element))
442            }
443        }
444    })
445}
446
447#[inline(never)]
448fn request_layout_component(
449    name: &'static str,
450    window: &mut Window,
451    cx: &mut App,
452    render: &mut dyn FnMut(&mut Window, &mut App) -> AnyElement,
453) -> (LayoutId, Option<AnyElement>) {
454    window.with_id(ElementId::from(name), |window| {
455        let mut element = render(window, cx);
456        let layout_id = element.request_layout(window, cx);
457        (layout_id, Some(element))
458    })
459}
460
461#[inline(never)]
462fn prepaint_view(
463    entity_id: EntityId,
464    global_id: Option<&GlobalElementId>,
465    bounds: Bounds<Pixels>,
466    element: &mut Option<AnyElement>,
467    window: &mut Window,
468    cx: &mut App,
469    render: &mut dyn FnMut(&mut Window, &mut App) -> AnyElement,
470) -> Option<AnyElement> {
471    window.set_view_id(entity_id);
472    window.with_rendered_view(entity_id, |window| {
473        if let Some(mut element) = element.take() {
474            element.prepaint(window, cx);
475            return Some(element);
476        }
477
478        window.with_element_state::<ViewElementState, _>(
479            global_id.unwrap(),
480            |element_state, window| {
481                let content_mask = window.content_mask();
482                let text_style = window.text_style();
483
484                if let Some(mut element_state) = element_state
485                    && element_state.cache_key.bounds == bounds
486                    && element_state.cache_key.content_mask == content_mask
487                    && element_state.cache_key.text_style == text_style
488                    && !window.dirty_views.contains(&entity_id)
489                    && !window.refreshing
490                {
491                    let prepaint_start = window.prepaint_index();
492                    window.reuse_prepaint(element_state.prepaint_range.clone());
493                    cx.entities
494                        .extend_accessed(&element_state.accessed_entities);
495                    let prepaint_end = window.prepaint_index();
496                    element_state.prepaint_range = prepaint_start..prepaint_end;
497
498                    return (None, element_state);
499                }
500
501                let refreshing = mem::replace(&mut window.refreshing, true);
502                let prepaint_start = window.prepaint_index();
503                let (element, accessed_entities) = cx.detect_accessed_entities(|cx| {
504                    let mut element = render(window, cx);
505                    element.layout_as_root(Size::<AvailableSpace>::from(bounds.size), window, cx);
506                    element.prepaint_at(bounds.origin, window, cx);
507                    element
508                });
509
510                let prepaint_end = window.prepaint_index();
511                window.refreshing = refreshing;
512
513                (
514                    Some(element),
515                    ViewElementState {
516                        accessed_entities,
517                        prepaint_range: prepaint_start..prepaint_end,
518                        paint_range: PaintIndex::default()..PaintIndex::default(),
519                        cache_key: ViewElementCacheKey {
520                            bounds,
521                            content_mask,
522                            text_style,
523                        },
524                    },
525                )
526            },
527        )
528    })
529}
530
531#[inline(never)]
532fn prepaint_component(
533    name: &'static str,
534    element: &mut Option<AnyElement>,
535    window: &mut Window,
536    cx: &mut App,
537) -> Option<AnyElement> {
538    window.with_id(ElementId::from(name), |window| {
539        element.as_mut().unwrap().prepaint(window, cx);
540    });
541    Some(element.take().unwrap())
542}
543
544#[inline(never)]
545fn paint_view(
546    entity_id: EntityId,
547    cached: bool,
548    global_id: Option<&GlobalElementId>,
549    element: &mut Option<AnyElement>,
550    window: &mut Window,
551    cx: &mut App,
552) {
553    window.with_rendered_view(entity_id, |window| {
554        let caching_disabled = window.is_inspector_picking(cx);
555        if cached && !caching_disabled {
556            window.with_element_state::<ViewElementState, _>(
557                global_id.unwrap(),
558                |element_state, window| {
559                    let mut element_state = element_state.unwrap();
560
561                    let paint_start = window.paint_index();
562
563                    if let Some(element) = element {
564                        let refreshing = mem::replace(&mut window.refreshing, true);
565                        element.paint(window, cx);
566                        window.refreshing = refreshing;
567                    } else {
568                        window.reuse_paint(element_state.paint_range.clone());
569                    }
570
571                    let paint_end = window.paint_index();
572                    element_state.paint_range = paint_start..paint_end;
573
574                    ((), element_state)
575                },
576            )
577        } else {
578            element.as_mut().unwrap().paint(window, cx);
579        }
580    });
581}
582
583#[inline(never)]
584fn paint_component(
585    name: &'static str,
586    element: &mut Option<AnyElement>,
587    window: &mut Window,
588    cx: &mut App,
589) {
590    window.with_id(ElementId::Name(name.into()), |window| {
591        element.as_mut().unwrap().paint(window, cx);
592    });
593}