Skip to main content

gpui/
view.rs

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