Skip to main content

gpui_authoring/
view.rs

1use crate::{
2    AnyElement, AnyEntity, AnyWeakEntity, App, Bounds, ContentMask, Context, Element, ElementId,
3    Entity, EntityId, GlobalElementId, IntoElement, LayoutId, PaintIndex, Pixels,
4    PrepaintStateIndex, Render, RenderOnce, Style, StyleRefinement, TextStyle, WeakEntity,
5};
6use crate::{Empty, Window};
7use anyhow::Result;
8use collections::FxHashSet;
9use refineable::Refineable;
10use std::mem;
11use std::{
12    any::{TypeId, type_name},
13    fmt,
14    ops::Range,
15};
16
17/// A dynamically-typed view handle that can be downcast to a specific `Entity<V>`.
18///
19/// This is the type-erased counterpart to [`ViewElement`]: it holds an entity plus
20/// a function pointer to its render, and is itself a [`View`], so embedding it as an
21/// element goes through the same [`ViewElement`] machinery as any other view.
22#[derive(Clone, Debug)]
23pub struct AnyView {
24    entity: AnyEntity,
25    render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
26    view_type_name: &'static str,
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            view_type_name: type_name::<V>(),
35        }
36    }
37}
38
39impl AnyView {
40    /// Embed this view as a cached [`ViewElement`] laid out at `style`.
41    ///
42    /// The rendered subtree is recycled from the previous frame unless
43    /// [Context::notify] was called on the backing entity since it was rendered
44    /// (or [Window::refresh] is called, which ignores caching).
45    pub fn cached(self, style: StyleRefinement) -> ViewElement<AnyView> {
46        self.into_element().cached(style)
47    }
48
49    /// Convert this to a weak handle.
50    pub fn downgrade(&self) -> AnyWeakView {
51        AnyWeakView {
52            entity: self.entity.downgrade(),
53            render: self.render,
54            view_type_name: self.view_type_name,
55        }
56    }
57
58    /// Convert this to a [Entity] of a specific type.
59    /// If this handle does not contain a view of the specified type, returns itself in an `Err` variant.
60    pub fn downcast<T: 'static>(self) -> Result<Entity<T>, Self> {
61        match self.entity.downcast() {
62            Ok(entity) => Ok(entity),
63            Err(entity) => Err(Self {
64                entity,
65                render: self.render,
66                view_type_name: self.view_type_name,
67            }),
68        }
69    }
70
71    /// Gets the [TypeId] of the underlying view.
72    pub fn entity_type(&self) -> TypeId {
73        self.entity.entity_type
74    }
75
76    /// The [`EntityId`] of this view.
77    pub fn entity_id(&self) -> EntityId {
78        self.entity.entity_id()
79    }
80}
81
82impl PartialEq for AnyView {
83    fn eq(&self, other: &Self) -> bool {
84        self.entity == other.entity
85    }
86}
87
88impl Eq for AnyView {}
89
90/// `AnyView` is the type-erased [`View`]: its `render` is a function pointer rather
91/// than a concrete type, but it participates in the reactive graph exactly like any
92/// other view via [`ViewElement`].
93impl View for AnyView {
94    fn entity_id(&self) -> Option<EntityId> {
95        Some(self.entity.entity_id())
96    }
97
98    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
99        (self.render)(&self, window, cx)
100    }
101}
102
103impl<V: 'static + Render> IntoElement for Entity<V> {
104    type Element = ViewElement<Entity<V>>;
105
106    fn into_element(self) -> Self::Element {
107        ViewElement::with_type_name(self, type_name::<V>())
108    }
109
110    #[inline(never)]
111    fn into_any_element(self) -> AnyElement {
112        self.into_element().into_any()
113    }
114}
115
116impl IntoElement for AnyView {
117    type Element = ViewElement<AnyView>;
118
119    fn into_element(self) -> Self::Element {
120        let view_type_name = self.view_type_name;
121        ViewElement::with_type_name(self, view_type_name)
122    }
123}
124
125/// A weak, dynamically-typed view handle.
126pub struct AnyWeakView {
127    entity: AnyWeakEntity,
128    render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
129    view_type_name: &'static str,
130}
131
132impl AnyWeakView {
133    /// Upgrade to a strong `AnyView` handle, if the view is still alive.
134    pub fn upgrade(&self) -> Option<AnyView> {
135        let entity = self.entity.upgrade()?;
136        Some(AnyView {
137            entity,
138            render: self.render,
139            view_type_name: self.view_type_name,
140        })
141    }
142}
143
144impl<V: 'static + Render> From<WeakEntity<V>> for AnyWeakView {
145    fn from(view: WeakEntity<V>) -> Self {
146        AnyWeakView {
147            entity: view.into(),
148            render: any_view::render::<V>,
149            view_type_name: type_name::<V>(),
150        }
151    }
152}
153
154impl PartialEq for AnyWeakView {
155    fn eq(&self, other: &Self) -> bool {
156        self.entity == other.entity
157    }
158}
159
160impl std::fmt::Debug for AnyWeakView {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        f.debug_struct("AnyWeakView")
163            .field("entity_id", &self.entity.entity_id)
164            .finish_non_exhaustive()
165    }
166}
167
168mod any_view {
169    use crate::{AnyElement, AnyView, App, IntoElement, Render, Window};
170
171    pub(crate) fn render<V: 'static + Render>(
172        view: &AnyView,
173        window: &mut Window,
174        cx: &mut App,
175    ) -> AnyElement {
176        let view = view.clone().downcast::<V>().unwrap();
177        // Record the view's Render type name so the accessibility debug dump can
178        // attribute nodes to the view that produced them.
179        #[cfg(debug_assertions)]
180        window
181            .core
182            .a11y
183            .view_type_names
184            .insert(view.entity_id(), std::any::type_name::<V>());
185        view.update(cx, |view, cx| view.render(window, cx).into_any_element())
186    }
187}
188
189/// A renderable that participates in GPUI's reactive graph — the unifying model
190/// behind [`Render`] and [`RenderOnce`].
191///
192/// When `entity_id()` returns `Some`, that id becomes the view's identity: it gets
193/// a unique element-id space (so internal `use_state` / `.id(..)` never collide
194/// across siblings) and `cx.notify()` on that entity re-renders only this view's
195/// subtree. `None` behaves like a stateless component.
196///
197/// You rarely implement `View` directly. `Entity<T: Render>` and any `T: RenderOnce`
198/// get a blanket impl below; implement it by hand only when a component needs both
199/// parent-supplied props *and* a backing entity for identity.
200pub trait View: 'static + Sized {
201    /// This view's identity, if it has one. A view typically holds the backing
202    /// entity as a field and returns its [`EntityId`] here.
203    ///
204    /// The id becomes this view's [`ElementId`], so two views keyed on the same
205    /// entity must not be rendered at the same position in the element tree
206    /// (e.g. as siblings under the same parent): their internal element state
207    /// (`use_state`, scroll offsets, etc.) would silently collide. Nesting is
208    /// fine — the id is scoped by the parent path.
209    fn entity_id(&self) -> Option<EntityId>;
210
211    /// Render this view into an element tree, consuming `self`.
212    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement;
213}
214
215/// A stateless component (`RenderOnce`) is a `View` with no identity.
216impl<T: RenderOnce> View for T {
217    fn entity_id(&self) -> Option<EntityId> {
218        None
219    }
220
221    #[inline]
222    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
223        RenderOnce::render(self, window, cx)
224    }
225}
226
227/// An entity that renders itself (`Render`) is a `View` keyed on its own id.
228impl<T: Render> View for Entity<T> {
229    fn entity_id(&self) -> Option<EntityId> {
230        Some(Entity::entity_id(self))
231    }
232
233    #[inline]
234    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
235        self.update(cx, |this, cx| {
236            Render::render(this, window, cx).into_any_element()
237        })
238    }
239}
240
241impl<T: Render> Entity<T> {
242    /// Embed this entity as a cached [`ViewElement`] laid out at `style`.
243    ///
244    /// The rendered subtree is reused until the entity is notified (or the
245    /// cached bounds / text style change). Caching requires a definite size:
246    /// a cached view is laid out from `style` and is *not* measured from its
247    /// contents. Use [`ViewElement::new`] (or `.child(entity)`) for the
248    /// uncached case.
249    #[track_caller]
250    pub fn cached(self, style: StyleRefinement) -> ViewElement<Entity<T>> {
251        ViewElement::with_type_name(self, type_name::<T>()).cached(style)
252    }
253}
254
255/// The element type for [`View`] implementations. Wraps a `View` and hooks it
256/// into layout, prepaint, and paint. Constructed via [`ViewElement::new`].
257#[doc(hidden)]
258pub struct ViewElement<V: View> {
259    view: Option<V>,
260    entity_id: Option<EntityId>,
261    cached_style: Option<StyleRefinement>,
262    #[cfg(feature = "profiler")]
263    view_type_name: &'static str,
264    #[cfg(debug_assertions)]
265    source: &'static core::panic::Location<'static>,
266}
267
268impl<V: View> ViewElement<V> {
269    /// Wrap a [`View`] as an element.
270    #[track_caller]
271    pub fn new(view: V) -> Self {
272        Self::with_type_name(view, type_name::<V>())
273    }
274
275    /// Wraps a view whose draw time profiling attributes to `view_type_name`,
276    /// such as an entity's type rather than [`Entity`]'s.
277    #[track_caller]
278    #[cfg_attr(not(feature = "profiler"), expect(unused_variables))]
279    pub(crate) fn with_type_name(view: V, view_type_name: &'static str) -> Self {
280        let entity_id = view.entity_id();
281        ViewElement {
282            entity_id,
283            cached_style: None,
284            #[cfg(feature = "profiler")]
285            view_type_name,
286            view: Some(view),
287            #[cfg(debug_assertions)]
288            source: core::panic::Location::caller(),
289        }
290    }
291
292    /// Enable caching of this view's rendered subtree, laid out at `style`.
293    /// The composer supplies the layout style because caching skips rendering
294    /// the contents to measure them.
295    ///
296    /// Crate-private on purpose: caching is only sound for entity-backed views,
297    /// where [`Context::notify`] is the contract that busts the cache. A stateless
298    /// view has no such contract, so a frozen subtree could never be invalidated.
299    /// Reach this through [`Entity::cached`] or [`AnyView::cached`], which are
300    /// entity-backed by construction.
301    pub(crate) fn cached(mut self, style: StyleRefinement) -> Self {
302        self.cached_style = Some(style);
303        self
304    }
305}
306
307impl<V: View> IntoElement for ViewElement<V> {
308    type Element = Self;
309
310    fn into_element(self) -> Self::Element {
311        self
312    }
313}
314
315pub(crate) struct ViewElementState {
316    prepaint_range: Range<PrepaintStateIndex>,
317    paint_range: Range<PaintIndex>,
318    cache_key: ViewElementCacheKey,
319    accessed_entities: FxHashSet<EntityId>,
320}
321
322pub(crate) struct ViewElementCacheKey {
323    bounds: Bounds<Pixels>,
324    content_mask: ContentMask<Pixels>,
325    text_style: TextStyle,
326}
327
328impl<V: View> Element for ViewElement<V> {
329    type RequestLayoutState = Option<AnyElement>;
330    type PrepaintState = Option<AnyElement>;
331
332    fn id(&self) -> Option<ElementId> {
333        self.entity_id.map(ElementId::View)
334    }
335
336    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
337        #[cfg(debug_assertions)]
338        return Some(self.source);
339
340        #[cfg(not(debug_assertions))]
341        return None;
342    }
343
344    fn request_layout(
345        &mut self,
346        _id: Option<&GlobalElementId>,
347        window: &mut Window,
348        cx: &mut App,
349    ) -> (LayoutId, Self::RequestLayoutState) {
350        if let Some(entity_id) = self.entity_id {
351            // Stateful path: create a reactive boundary.
352            #[cfg(feature = "profiler")]
353            let start = window.core.window_profiler.begin_view();
354            let view = &mut self.view;
355            let layout = request_layout_view(
356                entity_id,
357                self.cached_style.as_ref(),
358                window,
359                cx,
360                &mut |window, cx| view.take().unwrap().render(window, cx).into_any_element(),
361            );
362            #[cfg(feature = "profiler")]
363            window.core.window_profiler.end_view(
364                start,
365                self.view_type_name,
366                crate::profiler::ViewPhase::RequestLayout,
367            );
368            layout
369        } else {
370            // Stateless path: isolate subtree via type name (no entity identity).
371            request_layout_component(type_name::<V>(), window, cx, &mut |window, cx| {
372                self.view
373                    .take()
374                    .unwrap()
375                    .render(window, cx)
376                    .into_any_element()
377            })
378        }
379    }
380
381    fn prepaint(
382        &mut self,
383        global_id: Option<&GlobalElementId>,
384        bounds: Bounds<Pixels>,
385        element: &mut Self::RequestLayoutState,
386        window: &mut Window,
387        cx: &mut App,
388    ) -> Option<AnyElement> {
389        if let Some(entity_id) = self.entity_id {
390            // Stateful path.
391            #[cfg(feature = "profiler")]
392            let start = window.core.window_profiler.begin_view();
393            window.set_view_id(entity_id);
394            let prepainted = window.with_rendered_view(entity_id, |window| {
395                if let Some(mut element) = element.take() {
396                    element.prepaint(window, cx);
397                    return Some(element);
398                }
399
400                prepaint_cached_view(entity_id, global_id, bounds, window, cx, |window, cx| {
401                    self.view
402                        .take()
403                        .unwrap()
404                        .render(window, cx)
405                        .into_any_element()
406                })
407            });
408            #[cfg(feature = "profiler")]
409            window.core.window_profiler.end_view(
410                start,
411                self.view_type_name,
412                crate::profiler::ViewPhase::Prepaint,
413            );
414            prepainted
415        } else {
416            // Stateless path: just prepaint the element.
417            prepaint_component(type_name::<V>(), element, window, cx)
418        }
419    }
420
421    fn paint(
422        &mut self,
423        global_id: Option<&GlobalElementId>,
424        _bounds: Bounds<Pixels>,
425        _request_layout: &mut Self::RequestLayoutState,
426        element: &mut Self::PrepaintState,
427        window: &mut Window,
428        cx: &mut App,
429    ) {
430        if let Some(entity_id) = self.entity_id {
431            // Stateful path.
432            #[cfg(feature = "profiler")]
433            let start = window.core.window_profiler.begin_view();
434            paint_view(
435                entity_id,
436                self.cached_style.is_some(),
437                global_id,
438                element,
439                window,
440                cx,
441            );
442            #[cfg(feature = "profiler")]
443            window.core.window_profiler.end_view(
444                start,
445                self.view_type_name,
446                crate::profiler::ViewPhase::Paint,
447            );
448        } else {
449            // Stateless path: just paint the element.
450            paint_component(std::any::type_name::<V>(), element, window, cx);
451        }
452    }
453}
454
455/// A view that renders nothing
456pub struct EmptyView;
457
458impl Render for EmptyView {
459    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
460        Empty
461    }
462}
463
464/// Prepaints a subtree that can be reused from the previous frame.
465///
466/// The subtree is built by `build` and cached against `entity_id`, the bounds it
467/// was laid out at, the content mask and text style in effect, and the element id
468/// of the caller. On a hit the previous frame's prepaint is replayed and `build`
469/// is not called at all.
470///
471/// Returns the new subtree, or `None` when the previous frame's was reused.
472pub(crate) fn prepaint_cached_view(
473    entity_id: EntityId,
474    global_id: Option<&GlobalElementId>,
475    bounds: Bounds<Pixels>,
476    window: &mut Window,
477    cx: &mut App,
478    build: impl FnOnce(&mut Window, &mut App) -> AnyElement,
479) -> Option<AnyElement> {
480    window.with_element_state::<ViewElementState, _>(global_id.unwrap(), |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.frame_state.dirty_views.contains(&entity_id)
489            && !window.core.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.core.refreshing, true);
502        let prepaint_start = window.prepaint_index();
503        let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
504            let mut element = build(window, cx);
505            element.layout_as_root(bounds.size.into(), window, cx);
506            element.prepaint_at(bounds.origin, window, cx);
507            element
508        });
509
510        let prepaint_end = window.prepaint_index();
511        window.core.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#[inline(never)]
530fn request_layout_view(
531    entity_id: EntityId,
532    cached_style: Option<&StyleRefinement>,
533    window: &mut Window,
534    cx: &mut App,
535    render: &mut dyn FnMut(&mut Window, &mut App) -> AnyElement,
536) -> (LayoutId, Option<AnyElement>) {
537    window.with_rendered_view(entity_id, |window| {
538        let caching_disabled = window.is_inspector_picking(cx);
539        match cached_style {
540            Some(style) if !caching_disabled => {
541                let mut root_style = Style::default();
542                root_style.refine(style);
543                let layout_id = window.request_layout(root_style, None, cx);
544                (layout_id, None)
545            }
546            _ => {
547                let mut element = render(window, cx);
548                let layout_id = element.request_layout(window, cx);
549                (layout_id, Some(element))
550            }
551        }
552    })
553}
554
555#[inline(never)]
556fn request_layout_component(
557    name: &'static str,
558    window: &mut Window,
559    cx: &mut App,
560    render: &mut dyn FnMut(&mut Window, &mut App) -> AnyElement,
561) -> (LayoutId, Option<AnyElement>) {
562    window.with_id(ElementId::from(name), |window| {
563        let mut element = render(window, cx);
564        let layout_id = element.request_layout(window, cx);
565        (layout_id, Some(element))
566    })
567}
568
569#[inline(never)]
570fn prepaint_component(
571    name: &'static str,
572    element: &mut Option<AnyElement>,
573    window: &mut Window,
574    cx: &mut App,
575) -> Option<AnyElement> {
576    window.with_id(ElementId::from(name), |window| {
577        element.as_mut().unwrap().prepaint(window, cx);
578    });
579    Some(element.take().unwrap())
580}
581
582#[inline(never)]
583pub(crate) fn paint_view(
584    entity_id: EntityId,
585    cached: bool,
586    global_id: Option<&GlobalElementId>,
587    element: &mut Option<AnyElement>,
588    window: &mut Window,
589    cx: &mut App,
590) {
591    window.with_rendered_view(entity_id, |window| {
592        let caching_disabled = window.is_inspector_picking(cx);
593        if cached && !caching_disabled {
594            window.with_element_state::<ViewElementState, _>(
595                global_id.unwrap(),
596                |element_state, window| {
597                    let mut element_state = element_state.unwrap();
598
599                    let paint_start = window.paint_index();
600
601                    if let Some(element) = element {
602                        let refreshing = mem::replace(&mut window.core.refreshing, true);
603                        element.paint(window, cx);
604                        window.core.refreshing = refreshing;
605                    } else {
606                        window.reuse_paint(element_state.paint_range.clone());
607                    }
608
609                    let paint_end = window.paint_index();
610                    element_state.paint_range = paint_start..paint_end;
611
612                    ((), element_state)
613                },
614            )
615        } else {
616            element.as_mut().unwrap().paint(window, cx);
617        }
618    });
619}
620
621#[inline(never)]
622fn paint_component(
623    name: &'static str,
624    element: &mut Option<AnyElement>,
625    window: &mut Window,
626    cx: &mut App,
627) {
628    window.with_id(ElementId::Name(name.into()), |window| {
629        element.as_mut().unwrap().paint(window, cx);
630    });
631}
632
633#[cfg(test)]
634mod tests {
635    use std::{cell::Cell, rc::Rc};
636
637    use crate::{
638        AnyWindowHandle, AppContext as _, Context, Entity, IntoElement, ParentElement as _, Render,
639        StyleRefinement, Styled as _, TestAppContext, Window, div, green, px, size,
640    };
641
642    /// Counts the frames it renders in, so a test can tell a cache hit from a
643    /// rebuild.
644    struct CountedView {
645        renders: Rc<Cell<usize>>,
646    }
647
648    impl Render for CountedView {
649        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
650            self.renders.set(self.renders.get() + 1);
651            div().w(px(10.)).h(px(10.)).bg(green())
652        }
653    }
654
655    struct CachedViewHost {
656        view: Entity<CountedView>,
657    }
658
659    impl Render for CachedViewHost {
660        fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
661            div().size_full().child(
662                self.view
663                    .clone()
664                    .cached(StyleRefinement::default().size_full()),
665            )
666        }
667    }
668
669    /// Draws the window, returning how many quads the frame painted.
670    fn draw(cx: &mut TestAppContext, window: AnyWindowHandle) -> usize {
671        cx.update_window(window, |_, window, cx| {
672            window.draw(cx).clear(cx);
673            window.frame_state.rendered_frame.scene.quads.len()
674        })
675        .unwrap()
676    }
677
678    #[gpui::test]
679    fn a_cached_view_reuses_its_subtree_until_it_is_notified_or_resized(cx: &mut TestAppContext) {
680        let renders = Rc::new(Cell::new(0));
681        let view = cx.new({
682            let renders = renders.clone();
683            move |_| CountedView { renders }
684        });
685        let window: AnyWindowHandle = cx
686            .add_window({
687                let view = view.clone();
688                move |_, _| CachedViewHost { view }
689            })
690            .into();
691
692        let first = draw(cx, window);
693        assert_eq!(renders.get(), 1, "the first frame renders the view");
694        assert!(first > 0, "the view's content is painted");
695
696        let second = draw(cx, window);
697        assert_eq!(renders.get(), 1, "a clean cached view is not re-rendered");
698        assert_eq!(
699            second, first,
700            "the reused subtree is still painted into the scene"
701        );
702
703        view.update(cx, |_, cx| cx.notify());
704        draw(cx, window);
705        assert!(renders.get() > 1, "notifying the view re-renders it");
706
707        // The view is clean again, but the bounds it is drawn at changed.
708        let renders_before_resize = renders.get();
709        cx.simulate_window_resize(window, size(px(400.), px(300.)));
710        draw(cx, window);
711        assert!(
712            renders.get() > renders_before_resize,
713            "resizing the window re-renders the cached view"
714        );
715    }
716}