Skip to main content

gpui_core/
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, 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, 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    opacity: f32,
27}
28
29impl<V: Render> Element for Entity<V> {
30    type RequestLayoutState = AnyElement;
31    type PrepaintState = ();
32
33    fn id(&self) -> Option<ElementId> {
34        Some(ElementId::View(self.entity_id()))
35    }
36
37    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
38        None
39    }
40
41    fn request_layout(
42        &mut self,
43        _id: Option<&GlobalElementId>,
44        _inspector_id: Option<&InspectorElementId>,
45        window: &mut Window,
46        cx: &mut App,
47    ) -> (LayoutId, Self::RequestLayoutState) {
48        let mut element = self.update(cx, |view, cx| view.render(window, cx).into_any_element());
49        let layout_id = window.with_rendered_view(self.entity_id(), |window| {
50            element.request_layout(window, cx)
51        });
52        (layout_id, element)
53    }
54
55    fn prepaint(
56        &mut self,
57        _id: Option<&GlobalElementId>,
58        _inspector_id: Option<&InspectorElementId>,
59        _: Bounds<Pixels>,
60        element: &mut Self::RequestLayoutState,
61        window: &mut Window,
62        cx: &mut App,
63    ) {
64        window.set_view_id(self.entity_id());
65        window.with_rendered_view(self.entity_id(), |window| element.prepaint(window, cx));
66    }
67
68    fn paint(
69        &mut self,
70        _id: Option<&GlobalElementId>,
71        _inspector_id: Option<&InspectorElementId>,
72        _: Bounds<Pixels>,
73        element: &mut Self::RequestLayoutState,
74        _: &mut Self::PrepaintState,
75        window: &mut Window,
76        cx: &mut App,
77    ) {
78        window.with_rendered_view(self.entity_id(), |window| element.paint(window, cx));
79    }
80}
81
82/// A dynamically-typed handle to a view, which can be downcast to a [Entity] for a specific type.
83#[derive(Clone, Debug)]
84pub struct AnyView {
85    entity: AnyEntity,
86    render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
87    cached_style: Option<Rc<StyleRefinement>>,
88}
89
90impl<V: Render> From<Entity<V>> for AnyView {
91    fn from(value: Entity<V>) -> Self {
92        AnyView {
93            entity: value.into_any(),
94            render: any_view::render::<V>,
95            cached_style: None,
96        }
97    }
98}
99
100impl AnyView {
101    /// Indicate that this view should be cached when using it as an element.
102    /// When using this method, the view's previous layout and paint will be recycled from the previous frame if [Context::notify] has not been called since it was rendered.
103    /// The one exception is when [Window::refresh] is called, in which case caching is ignored.
104    pub fn cached(mut self, style: StyleRefinement) -> Self {
105        self.cached_style = Some(style.into());
106        self
107    }
108
109    /// Convert this to a weak handle.
110    pub fn downgrade(&self) -> AnyWeakView {
111        AnyWeakView {
112            entity: self.entity.downgrade(),
113            render: self.render,
114        }
115    }
116
117    /// Convert this to a [Entity] of a specific type.
118    /// If this handle does not contain a view of the specified type, returns itself in an `Err` variant.
119    pub fn downcast<T: 'static>(self) -> Result<Entity<T>, Self> {
120        match self.entity.downcast() {
121            Ok(entity) => Ok(entity),
122            Err(entity) => Err(Self {
123                entity,
124                render: self.render,
125                cached_style: self.cached_style,
126            }),
127        }
128    }
129
130    /// Gets the [TypeId] of the underlying view.
131    pub fn entity_type(&self) -> TypeId {
132        self.entity.entity_type
133    }
134
135    /// Gets the entity id of this handle.
136    pub fn entity_id(&self) -> EntityId {
137        self.entity.entity_id()
138    }
139}
140
141impl PartialEq for AnyView {
142    fn eq(&self, other: &Self) -> bool {
143        self.entity == other.entity
144    }
145}
146
147impl Eq for AnyView {}
148
149impl Element for AnyView {
150    type RequestLayoutState = Option<AnyElement>;
151    type PrepaintState = Option<AnyElement>;
152
153    fn id(&self) -> Option<ElementId> {
154        Some(ElementId::View(self.entity_id()))
155    }
156
157    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
158        None
159    }
160
161    fn request_layout(
162        &mut self,
163        _id: Option<&GlobalElementId>,
164        _inspector_id: Option<&InspectorElementId>,
165        window: &mut Window,
166        cx: &mut App,
167    ) -> (LayoutId, Self::RequestLayoutState) {
168        window.with_rendered_view(self.entity_id(), |window| {
169            // Disable caching when inspecting so that mouse_hit_test has all hitboxes.
170            let caching_disabled = window.is_inspector_picking(cx);
171            match self.cached_style.as_ref() {
172                Some(style) if !caching_disabled => {
173                    let mut root_style = Style::default();
174                    root_style.refine(style);
175                    let layout_id = window.request_layout(root_style, None, cx);
176                    (layout_id, None)
177                }
178                _ => {
179                    let mut element = (self.render)(self, window, cx);
180                    let layout_id = element.request_layout(window, cx);
181                    (layout_id, Some(element))
182                }
183            }
184        })
185    }
186
187    fn prepaint(
188        &mut self,
189        global_id: Option<&GlobalElementId>,
190        _inspector_id: Option<&InspectorElementId>,
191        bounds: Bounds<Pixels>,
192        element: &mut Self::RequestLayoutState,
193        window: &mut Window,
194        cx: &mut App,
195    ) -> Option<AnyElement> {
196        window.set_view_id(self.entity_id());
197        window.with_rendered_view(self.entity_id(), |window| {
198            if let Some(mut element) = element.take() {
199                element.prepaint(window, cx);
200                return Some(element);
201            }
202
203            window.with_element_state::<AnyViewState, _>(
204                global_id.unwrap(),
205                |element_state, window| {
206                    let content_mask = window.content_mask();
207                    let text_style = window.text_style();
208                    let opacity = window.element_opacity();
209
210                    // Opacity is compared by bits so that a NaN opacity still matches itself
211                    // and the view keeps using its cache.
212                    if let Some(mut element_state) = element_state
213                        && element_state.cache_key.bounds == bounds
214                        && element_state.cache_key.content_mask == content_mask
215                        && element_state.cache_key.text_style == text_style
216                        && element_state.cache_key.opacity.to_bits() == opacity.to_bits()
217                        && !window.dirty_views.contains(&self.entity_id())
218                        && !window.refreshing
219                    {
220                        let prepaint_start = window.prepaint_index();
221                        window.reuse_prepaint(element_state.prepaint_range.clone());
222                        cx.entities
223                            .extend_accessed(&element_state.accessed_entities);
224                        let prepaint_end = window.prepaint_index();
225                        element_state.prepaint_range = prepaint_start..prepaint_end;
226
227                        return (None, element_state);
228                    }
229
230                    let refreshing = mem::replace(&mut window.refreshing, true);
231                    let prepaint_start = window.prepaint_index();
232                    let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
233                        let mut element = (self.render)(self, window, cx);
234                        element.layout_as_root(bounds.size.into(), window, cx);
235                        element.prepaint_at(bounds.origin, window, cx);
236                        element
237                    });
238
239                    let prepaint_end = window.prepaint_index();
240                    window.refreshing = refreshing;
241
242                    (
243                        Some(element),
244                        AnyViewState {
245                            accessed_entities,
246                            prepaint_range: prepaint_start..prepaint_end,
247                            paint_range: PaintIndex::default()..PaintIndex::default(),
248                            cache_key: ViewCacheKey {
249                                bounds,
250                                content_mask,
251                                text_style,
252                                opacity,
253                            },
254                        },
255                    )
256                },
257            )
258        })
259    }
260
261    fn paint(
262        &mut self,
263        global_id: Option<&GlobalElementId>,
264        _inspector_id: Option<&InspectorElementId>,
265        _bounds: Bounds<Pixels>,
266        _: &mut Self::RequestLayoutState,
267        element: &mut Self::PrepaintState,
268        window: &mut Window,
269        cx: &mut App,
270    ) {
271        window.with_rendered_view(self.entity_id(), |window| {
272            let caching_disabled = window.is_inspector_picking(cx);
273            if self.cached_style.is_some() && !caching_disabled {
274                window.with_element_state::<AnyViewState, _>(
275                    global_id.unwrap(),
276                    |element_state, window| {
277                        let mut element_state = element_state.unwrap();
278
279                        let paint_start = window.paint_index();
280                        let opacity = window.element_opacity();
281
282                        // Prepaint can only predict the opacity paint applies: an ancestor's
283                        // hover or drag-over style depends on this frame's hit test, which
284                        // isn't known until prepaint finishes. The key records what was
285                        // actually painted, and output replayed under a different opacity is
286                        // redrawn on the next frame.
287                        if let Some(element) = element {
288                            let refreshing = mem::replace(&mut window.refreshing, true);
289                            element.paint(window, cx);
290                            window.refreshing = refreshing;
291                            element_state.cache_key.opacity = opacity;
292                        } else {
293                            window.reuse_paint(element_state.paint_range.clone());
294                            if element_state.cache_key.opacity.to_bits() != opacity.to_bits() {
295                                window.request_animation_frame();
296                            }
297                        }
298
299                        let paint_end = window.paint_index();
300                        element_state.paint_range = paint_start..paint_end;
301
302                        ((), element_state)
303                    },
304                )
305            } else {
306                element.as_mut().unwrap().paint(window, cx);
307            }
308        });
309    }
310}
311
312impl<V: 'static + Render> IntoElement for Entity<V> {
313    type Element = Entity<V>;
314
315    fn into_element(self) -> Self::Element {
316        self
317    }
318}
319
320impl IntoElement for AnyView {
321    type Element = Self;
322
323    fn into_element(self) -> Self::Element {
324        self
325    }
326}
327
328/// A weak, dynamically-typed view handle that does not prevent the view from being released.
329pub struct AnyWeakView {
330    entity: AnyWeakEntity,
331    render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
332}
333
334impl AnyWeakView {
335    /// Convert to a strongly-typed handle if the referenced view has not yet been released.
336    pub fn upgrade(&self) -> Option<AnyView> {
337        let entity = self.entity.upgrade()?;
338        Some(AnyView {
339            entity,
340            render: self.render,
341            cached_style: None,
342        })
343    }
344}
345
346impl<V: 'static + Render> From<WeakEntity<V>> for AnyWeakView {
347    fn from(view: WeakEntity<V>) -> Self {
348        AnyWeakView {
349            entity: view.into(),
350            render: any_view::render::<V>,
351        }
352    }
353}
354
355impl PartialEq for AnyWeakView {
356    fn eq(&self, other: &Self) -> bool {
357        self.entity == other.entity
358    }
359}
360
361impl std::fmt::Debug for AnyWeakView {
362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363        f.debug_struct("AnyWeakView")
364            .field("entity_id", &self.entity.entity_id)
365            .finish_non_exhaustive()
366    }
367}
368
369mod any_view {
370    use crate::{AnyElement, AnyView, App, IntoElement, Render, Window};
371
372    pub(crate) fn render<V: 'static + Render>(
373        view: &AnyView,
374        window: &mut Window,
375        cx: &mut App,
376    ) -> AnyElement {
377        let view = view.clone().downcast::<V>().unwrap();
378        view.update(cx, |view, cx| view.render(window, cx).into_any_element())
379    }
380}
381
382/// A view that renders nothing
383pub struct EmptyView;
384
385impl Render for EmptyView {
386    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
387        Empty
388    }
389}