view.rs

  1use crate::{
  2    AnyElement, AnyEntity, AnyWeakEntity, App, Bounds, ContentMask, Context, Element, ElementId,
  3    Entity, EntityId, GlobalElementId, InspectorElementId, IntoElement, LayoutId, PaintIndex,
  4    Pixels, PrepaintStateIndex, Render, Style, StyleRefinement, Styled, TextStyle, WeakEntity,
  5};
  6use crate::{Empty, Window};
  7use anyhow::Result;
  8use collections::FxHashSet;
  9use refineable::Refineable;
 10use std::mem;
 11use std::rc::Rc;
 12use std::{any::TypeId, fmt, hash::Hash, ops::Range};
 13
 14struct AnyViewState {
 15    prepaint_range: Range<PrepaintStateIndex>,
 16    paint_range: Range<PaintIndex>,
 17    cache_key: ViewCacheKey,
 18    accessed_entities: FxHashSet<EntityId>,
 19}
 20
 21#[derive(Default)]
 22struct ViewCacheKey {
 23    bounds: Bounds<Pixels>,
 24    content_mask: ContentMask<Pixels>,
 25    text_style: TextStyle,
 26}
 27
 28/// A dynamically-typed view handle that can be downcast to a specific `Entity<V>`.
 29#[derive(Clone, Debug)]
 30pub struct AnyView {
 31    entity: AnyEntity,
 32    render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
 33    cached_style: Option<Rc<StyleRefinement>>,
 34}
 35
 36impl<V: Render> From<Entity<V>> for AnyView {
 37    fn from(value: Entity<V>) -> Self {
 38        AnyView {
 39            entity: value.into_any(),
 40            render: any_view::render::<V>,
 41            cached_style: None,
 42        }
 43    }
 44}
 45
 46impl AnyView {
 47    /// Enable caching with the given outer layout style.
 48    pub fn cached(mut self, style: StyleRefinement) -> Self {
 49        self.cached_style = Some(style.into());
 50        self
 51    }
 52
 53    /// Downgrade to a weak handle.
 54    pub fn downgrade(&self) -> AnyWeakView {
 55        AnyWeakView {
 56            entity: self.entity.downgrade(),
 57            render: self.render,
 58        }
 59    }
 60
 61    /// Downcast to a typed `Entity<T>`, returning `Err(self)` on type mismatch.
 62    pub fn downcast<T: 'static>(self) -> Result<Entity<T>, Self> {
 63        match self.entity.downcast() {
 64            Ok(entity) => Ok(entity),
 65            Err(entity) => Err(Self {
 66                entity,
 67                render: self.render,
 68                cached_style: self.cached_style,
 69            }),
 70        }
 71    }
 72
 73    /// The [`TypeId`] of the underlying view.
 74    pub fn entity_type(&self) -> TypeId {
 75        self.entity.entity_type
 76    }
 77
 78    /// The [`EntityId`] of this view.
 79    pub fn entity_id(&self) -> EntityId {
 80        self.entity.entity_id()
 81    }
 82}
 83
 84impl PartialEq for AnyView {
 85    fn eq(&self, other: &Self) -> bool {
 86        self.entity == other.entity
 87    }
 88}
 89
 90impl Eq for AnyView {}
 91
 92impl Element for AnyView {
 93    type RequestLayoutState = Option<AnyElement>;
 94    type PrepaintState = Option<AnyElement>;
 95
 96    fn id(&self) -> Option<ElementId> {
 97        Some(ElementId::View(self.entity_id()))
 98    }
 99
100    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
101        None
102    }
103
104    fn request_layout(
105        &mut self,
106        _id: Option<&GlobalElementId>,
107        _inspector_id: Option<&InspectorElementId>,
108        window: &mut Window,
109        cx: &mut App,
110    ) -> (LayoutId, Self::RequestLayoutState) {
111        window.with_rendered_view(self.entity_id(), |window| {
112            // Disable caching when inspecting so that mouse_hit_test has all hitboxes.
113            let caching_disabled = window.is_inspector_picking(cx);
114            match self.cached_style.as_ref() {
115                Some(style) if !caching_disabled => {
116                    let mut root_style = Style::default();
117                    root_style.refine(style);
118                    let layout_id = window.request_layout(root_style, None, cx);
119                    (layout_id, None)
120                }
121                _ => {
122                    let mut element = (self.render)(self, window, cx);
123                    let layout_id = element.request_layout(window, cx);
124                    (layout_id, Some(element))
125                }
126            }
127        })
128    }
129
130    fn prepaint(
131        &mut self,
132        global_id: Option<&GlobalElementId>,
133        _inspector_id: Option<&InspectorElementId>,
134        bounds: Bounds<Pixels>,
135        element: &mut Self::RequestLayoutState,
136        window: &mut Window,
137        cx: &mut App,
138    ) -> Option<AnyElement> {
139        window.set_view_id(self.entity_id());
140        window.with_rendered_view(self.entity_id(), |window| {
141            if let Some(mut element) = element.take() {
142                element.prepaint(window, cx);
143                return Some(element);
144            }
145
146            window.with_element_state::<AnyViewState, _>(
147                global_id.unwrap(),
148                |element_state, window| {
149                    let content_mask = window.content_mask();
150                    let text_style = window.text_style();
151
152                    if let Some(mut element_state) = element_state
153                        && element_state.cache_key.bounds == bounds
154                        && element_state.cache_key.content_mask == content_mask
155                        && element_state.cache_key.text_style == text_style
156                        && !window.dirty_views.contains(&self.entity_id())
157                        && !window.refreshing
158                    {
159                        let prepaint_start = window.prepaint_index();
160                        window.reuse_prepaint(element_state.prepaint_range.clone());
161                        cx.entities
162                            .extend_accessed(&element_state.accessed_entities);
163                        let prepaint_end = window.prepaint_index();
164                        element_state.prepaint_range = prepaint_start..prepaint_end;
165
166                        return (None, element_state);
167                    }
168
169                    let refreshing = mem::replace(&mut window.refreshing, true);
170                    let prepaint_start = window.prepaint_index();
171                    let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
172                        let mut element = (self.render)(self, window, cx);
173                        element.layout_as_root(bounds.size.into(), window, cx);
174                        element.prepaint_at(bounds.origin, window, cx);
175                        element
176                    });
177
178                    let prepaint_end = window.prepaint_index();
179                    window.refreshing = refreshing;
180
181                    (
182                        Some(element),
183                        AnyViewState {
184                            accessed_entities,
185                            prepaint_range: prepaint_start..prepaint_end,
186                            paint_range: PaintIndex::default()..PaintIndex::default(),
187                            cache_key: ViewCacheKey {
188                                bounds,
189                                content_mask,
190                                text_style,
191                            },
192                        },
193                    )
194                },
195            )
196        })
197    }
198
199    fn paint(
200        &mut self,
201        global_id: Option<&GlobalElementId>,
202        _inspector_id: Option<&InspectorElementId>,
203        _bounds: Bounds<Pixels>,
204        _: &mut Self::RequestLayoutState,
205        element: &mut Self::PrepaintState,
206        window: &mut Window,
207        cx: &mut App,
208    ) {
209        window.with_rendered_view(self.entity_id(), |window| {
210            let caching_disabled = window.is_inspector_picking(cx);
211            if self.cached_style.is_some() && !caching_disabled {
212                window.with_element_state::<AnyViewState, _>(
213                    global_id.unwrap(),
214                    |element_state, window| {
215                        let mut element_state = element_state.unwrap();
216
217                        let paint_start = window.paint_index();
218
219                        if let Some(element) = element {
220                            let refreshing = mem::replace(&mut window.refreshing, true);
221                            element.paint(window, cx);
222                            window.refreshing = refreshing;
223                        } else {
224                            window.reuse_paint(element_state.paint_range.clone());
225                        }
226
227                        let paint_end = window.paint_index();
228                        element_state.paint_range = paint_start..paint_end;
229
230                        ((), element_state)
231                    },
232                )
233            } else {
234                element.as_mut().unwrap().paint(window, cx);
235            }
236        });
237    }
238}
239
240impl<V: 'static + Render> IntoElement for Entity<V> {
241    type Element = AnyView;
242
243    fn into_element(self) -> Self::Element {
244        self.into()
245    }
246}
247
248impl IntoElement for AnyView {
249    type Element = Self;
250
251    fn into_element(self) -> Self::Element {
252        self
253    }
254}
255
256/// A weak, dynamically-typed view handle.
257pub struct AnyWeakView {
258    entity: AnyWeakEntity,
259    render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
260}
261
262impl AnyWeakView {
263    /// Upgrade to a strong `AnyView` handle, if the view is still alive.
264    pub fn upgrade(&self) -> Option<AnyView> {
265        let entity = self.entity.upgrade()?;
266        Some(AnyView {
267            entity,
268            render: self.render,
269            cached_style: None,
270        })
271    }
272}
273
274impl<V: 'static + Render> From<WeakEntity<V>> for AnyWeakView {
275    fn from(view: WeakEntity<V>) -> Self {
276        AnyWeakView {
277            entity: view.into(),
278            render: any_view::render::<V>,
279        }
280    }
281}
282
283impl PartialEq for AnyWeakView {
284    fn eq(&self, other: &Self) -> bool {
285        self.entity == other.entity
286    }
287}
288
289impl std::fmt::Debug for AnyWeakView {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        f.debug_struct("AnyWeakView")
292            .field("entity_id", &self.entity.entity_id)
293            .finish_non_exhaustive()
294    }
295}
296
297mod any_view {
298    use crate::{AnyElement, AnyView, App, IntoElement, Render, Window};
299
300    pub(crate) fn render<V: 'static + Render>(
301        view: &AnyView,
302        window: &mut Window,
303        cx: &mut App,
304    ) -> AnyElement {
305        let view = view.clone().downcast::<V>().unwrap();
306        view.update(cx, |view, cx| view.render(window, cx).into_any_element())
307    }
308}
309
310/// A renderable component that participates in GPUI's reactive graph.
311///
312/// When `entity()` returns `Some`, `cx.notify()` on that entity only
313/// re-renders this view's subtree. Implement this trait directly for
314/// full control, or use [`ComponentView`] / [`EntityView`] for the
315/// common stateless / stateful cases.
316pub trait View: 'static + Sized + Hash {
317    /// The entity type that backs this view's state.
318    type Entity: 'static;
319
320    /// The entity that backs this view, if any. `Some` creates a reactive boundary;
321    /// `None` behaves like a stateless component.
322    fn entity(&self) -> Option<Entity<Self::Entity>>;
323
324    /// Render this view into an element tree, consuming `self`.
325    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement;
326
327    /// Outer layout style for caching. `Some` enables caching; `None` disables it.
328    /// Defaults to full-size (`width: 100%, height: 100%`).
329    fn cache_style(&mut self, _window: &mut Window, _cx: &mut App) -> Option<StyleRefinement> {
330        Some(StyleRefinement::default().size_full())
331    }
332}
333
334/// A stateless component that renders an element tree without an entity.
335/// This is the [`View`] equivalent of [`RenderOnce`](crate::RenderOnce).
336pub trait ComponentView: 'static + Sized + Hash {
337    /// Render this component into an element tree, consuming `self`.
338    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement;
339
340    /// Outer layout style for caching. Defaults to `None` (no caching).
341    fn cache_style(&mut self, _window: &mut Window, _cx: &mut App) -> Option<StyleRefinement> {
342        None
343    }
344}
345
346impl<T: ComponentView> View for T {
347    type Entity = ();
348
349    fn entity(&self) -> Option<Entity<()>> {
350        None
351    }
352
353    #[inline]
354    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
355        ComponentView::render(self, window, cx)
356    }
357
358    #[inline]
359    fn cache_style(&mut self, window: &mut Window, cx: &mut App) -> Option<StyleRefinement> {
360        ComponentView::cache_style(self, window, cx)
361    }
362}
363
364/// A stateful entity that renders an element tree with a reactive boundary.
365/// This is the [`View`]-based replacement for [`Render`].
366///
367/// Gives `Entity<T>` a blanket [`View`] impl. Use `ViewElement::new(entity)`
368/// to convert an `Entity<T>` into a child element.
369pub trait EntityView: 'static + Sized {
370    /// Render this entity into an element tree.
371    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement;
372
373    /// Outer layout style for caching. Defaults to `None` (no caching).
374    fn cache_style(
375        &mut self,
376        _window: &mut Window,
377        _cx: &mut Context<Self>,
378    ) -> Option<StyleRefinement> {
379        None
380    }
381}
382
383impl<T: EntityView> View for Entity<T> {
384    type Entity = T;
385
386    fn entity(&self) -> Option<Entity<Self::Entity>> {
387        Some(self.clone())
388    }
389
390    #[inline]
391    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
392        self.update(cx, |this, cx| {
393            EntityView::render(this, window, cx).into_any_element()
394        })
395    }
396
397    #[inline]
398    fn cache_style(&mut self, window: &mut Window, cx: &mut App) -> Option<StyleRefinement> {
399        self.update(cx, |this, cx| EntityView::cache_style(this, window, cx))
400    }
401}
402
403/// The element type for [`View`] implementations. Wraps a `View` and hooks
404/// it into. Constructed via [`ViewElement::new`].
405#[doc(hidden)]
406pub struct ViewElement<V: View> {
407    view: Option<V>,
408    entity_id: Option<EntityId>,
409    props_hash: u64,
410    cached_style: Option<StyleRefinement>,
411    #[cfg(debug_assertions)]
412    source: &'static core::panic::Location<'static>,
413}
414
415impl<V: View> ViewElement<V> {
416    /// Wrap a [`View`] as an element.
417    #[track_caller]
418    pub fn new(view: V) -> Self {
419        use std::hash::Hasher;
420        let entity_id = view.entity().map(|e| e.entity_id());
421        let mut hasher = std::collections::hash_map::DefaultHasher::new();
422        view.hash(&mut hasher);
423        let props_hash = hasher.finish();
424        ViewElement {
425            entity_id,
426            props_hash,
427            cached_style: None,
428            view: Some(view),
429            #[cfg(debug_assertions)]
430            source: core::panic::Location::caller(),
431        }
432    }
433}
434
435impl<V: View> IntoElement for ViewElement<V> {
436    type Element = Self;
437
438    fn into_element(self) -> Self::Element {
439        self
440    }
441}
442
443struct ViewElementState {
444    prepaint_range: Range<PrepaintStateIndex>,
445    paint_range: Range<PaintIndex>,
446    cache_key: ViewElementCacheKey,
447    accessed_entities: FxHashSet<EntityId>,
448}
449
450struct ViewElementCacheKey {
451    bounds: Bounds<Pixels>,
452    content_mask: ContentMask<Pixels>,
453    text_style: TextStyle,
454    props_hash: u64,
455}
456
457impl<V: View> Element for ViewElement<V> {
458    type RequestLayoutState = Option<AnyElement>;
459    type PrepaintState = Option<AnyElement>;
460
461    fn id(&self) -> Option<ElementId> {
462        self.entity_id.map(ElementId::View)
463    }
464
465    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
466        #[cfg(debug_assertions)]
467        return Some(self.source);
468
469        #[cfg(not(debug_assertions))]
470        return None;
471    }
472
473    fn request_layout(
474        &mut self,
475        _id: Option<&GlobalElementId>,
476        _inspector_id: Option<&InspectorElementId>,
477        window: &mut Window,
478        cx: &mut App,
479    ) -> (LayoutId, Self::RequestLayoutState) {
480        if self.cached_style.is_none() {
481            if let Some(view) = self.view.as_mut() {
482                self.cached_style = view.cache_style(window, cx);
483            }
484        }
485
486        if let Some(entity_id) = self.entity_id {
487            // Stateful path: create a reactive boundary.
488            window.with_rendered_view(entity_id, |window| {
489                let caching_disabled = window.is_inspector_picking(cx);
490                match self.cached_style.as_ref() {
491                    Some(style) if !caching_disabled => {
492                        let mut root_style = Style::default();
493                        root_style.refine(style);
494                        let layout_id = window.request_layout(root_style, None, cx);
495                        (layout_id, None)
496                    }
497                    _ => {
498                        let mut element = self
499                            .view
500                            .take()
501                            .unwrap()
502                            .render(window, cx)
503                            .into_any_element();
504                        let layout_id = element.request_layout(window, cx);
505                        (layout_id, Some(element))
506                    }
507                }
508            })
509        } else {
510            // Stateless path: isolate subtree via type name (like Component<C>).
511            window.with_id(
512                ElementId::Name(std::any::type_name::<V>().into()),
513                |window| {
514                    let mut element = self
515                        .view
516                        .take()
517                        .unwrap()
518                        .render(window, cx)
519                        .into_any_element();
520                    let layout_id = element.request_layout(window, cx);
521                    (layout_id, Some(element))
522                },
523            )
524        }
525    }
526
527    fn prepaint(
528        &mut self,
529        global_id: Option<&GlobalElementId>,
530        _inspector_id: Option<&InspectorElementId>,
531        bounds: Bounds<Pixels>,
532        element: &mut Self::RequestLayoutState,
533        window: &mut Window,
534        cx: &mut App,
535    ) -> Option<AnyElement> {
536        if let Some(entity_id) = self.entity_id {
537            // Stateful path.
538            window.set_view_id(entity_id);
539            window.with_rendered_view(entity_id, |window| {
540                if let Some(mut element) = element.take() {
541                    element.prepaint(window, cx);
542                    return Some(element);
543                }
544
545                window.with_element_state::<ViewElementState, _>(
546                    global_id.unwrap(),
547                    |element_state, window| {
548                        let content_mask = window.content_mask();
549                        let text_style = window.text_style();
550
551                        if let Some(mut element_state) = element_state
552                            && element_state.cache_key.bounds == bounds
553                            && element_state.cache_key.content_mask == content_mask
554                            && element_state.cache_key.text_style == text_style
555                            && element_state.cache_key.props_hash == self.props_hash
556                            && !window.dirty_views.contains(&entity_id)
557                            && !window.refreshing
558                        {
559                            let prepaint_start = window.prepaint_index();
560                            window.reuse_prepaint(element_state.prepaint_range.clone());
561                            cx.entities
562                                .extend_accessed(&element_state.accessed_entities);
563                            let prepaint_end = window.prepaint_index();
564                            element_state.prepaint_range = prepaint_start..prepaint_end;
565
566                            return (None, element_state);
567                        }
568
569                        let refreshing = mem::replace(&mut window.refreshing, true);
570                        let prepaint_start = window.prepaint_index();
571                        let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
572                            let mut element = self
573                                .view
574                                .take()
575                                .unwrap()
576                                .render(window, cx)
577                                .into_any_element();
578                            element.layout_as_root(bounds.size.into(), window, cx);
579                            element.prepaint_at(bounds.origin, window, cx);
580                            element
581                        });
582
583                        let prepaint_end = window.prepaint_index();
584                        window.refreshing = refreshing;
585
586                        (
587                            Some(element),
588                            ViewElementState {
589                                accessed_entities,
590                                prepaint_range: prepaint_start..prepaint_end,
591                                paint_range: PaintIndex::default()..PaintIndex::default(),
592                                cache_key: ViewElementCacheKey {
593                                    bounds,
594                                    content_mask,
595                                    text_style,
596                                    props_hash: self.props_hash,
597                                },
598                            },
599                        )
600                    },
601                )
602            })
603        } else {
604            // Stateless path: just prepaint the element.
605            window.with_id(
606                ElementId::Name(std::any::type_name::<V>().into()),
607                |window| {
608                    element.as_mut().unwrap().prepaint(window, cx);
609                },
610            );
611            Some(element.take().unwrap())
612        }
613    }
614
615    fn paint(
616        &mut self,
617        global_id: Option<&GlobalElementId>,
618        _inspector_id: Option<&InspectorElementId>,
619        _bounds: Bounds<Pixels>,
620        _request_layout: &mut Self::RequestLayoutState,
621        element: &mut Self::PrepaintState,
622        window: &mut Window,
623        cx: &mut App,
624    ) {
625        if let Some(entity_id) = self.entity_id {
626            // Stateful path.
627            window.with_rendered_view(entity_id, |window| {
628                let caching_disabled = window.is_inspector_picking(cx);
629                if self.cached_style.is_some() && !caching_disabled {
630                    window.with_element_state::<ViewElementState, _>(
631                        global_id.unwrap(),
632                        |element_state, window| {
633                            let mut element_state = element_state.unwrap();
634
635                            let paint_start = window.paint_index();
636
637                            if let Some(element) = element {
638                                let refreshing = mem::replace(&mut window.refreshing, true);
639                                element.paint(window, cx);
640                                window.refreshing = refreshing;
641                            } else {
642                                window.reuse_paint(element_state.paint_range.clone());
643                            }
644
645                            let paint_end = window.paint_index();
646                            element_state.paint_range = paint_start..paint_end;
647
648                            ((), element_state)
649                        },
650                    )
651                } else {
652                    element.as_mut().unwrap().paint(window, cx);
653                }
654            });
655        } else {
656            // Stateless path: just paint the element.
657            window.with_id(
658                ElementId::Name(std::any::type_name::<V>().into()),
659                |window| {
660                    element.as_mut().unwrap().paint(window, cx);
661                },
662            );
663        }
664    }
665}
666
667/// A view that renders nothing
668pub struct EmptyView;
669
670impl Render for EmptyView {
671    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
672        Empty
673    }
674}