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}
 27
 28/// A dynamically-typed handle to a view, which can be downcast to a [Entity] for a specific type.
 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    /// Indicate that this view should be cached when using it as an element.
 48    /// 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.
 49    /// The one exception is when [Window::refresh] is called, in which case caching is ignored.
 50    pub fn cached(mut self, style: StyleRefinement) -> Self {
 51        self.cached_style = Some(style.into());
 52        self
 53    }
 54
 55    /// Convert this to a weak handle.
 56    pub fn downgrade(&self) -> AnyWeakView {
 57        AnyWeakView {
 58            entity: self.entity.downgrade(),
 59            render: self.render,
 60        }
 61    }
 62
 63    /// Convert this to a [Entity] of a specific type.
 64    /// If this handle does not contain a view of the specified type, returns itself in an `Err` variant.
 65    pub fn downcast<T: 'static>(self) -> Result<Entity<T>, Self> {
 66        match self.entity.downcast() {
 67            Ok(entity) => Ok(entity),
 68            Err(entity) => Err(Self {
 69                entity,
 70                render: self.render,
 71                cached_style: self.cached_style,
 72            }),
 73        }
 74    }
 75
 76    /// Gets the [TypeId] of the underlying view.
 77    pub fn entity_type(&self) -> TypeId {
 78        self.entity.entity_type
 79    }
 80
 81    /// Gets the entity id of this handle.
 82    pub fn entity_id(&self) -> EntityId {
 83        self.entity.entity_id()
 84    }
 85}
 86
 87impl PartialEq for AnyView {
 88    fn eq(&self, other: &Self) -> bool {
 89        self.entity == other.entity
 90    }
 91}
 92
 93impl Eq for AnyView {}
 94
 95impl Element for AnyView {
 96    type RequestLayoutState = Option<AnyElement>;
 97    type PrepaintState = Option<AnyElement>;
 98
 99    fn id(&self) -> Option<ElementId> {
100        Some(ElementId::View(self.entity_id()))
101    }
102
103    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
104        None
105    }
106
107    fn request_layout(
108        &mut self,
109        _id: Option<&GlobalElementId>,
110        _inspector_id: Option<&InspectorElementId>,
111        window: &mut Window,
112        cx: &mut App,
113    ) -> (LayoutId, Self::RequestLayoutState) {
114        window.with_rendered_view(self.entity_id(), |window| {
115            // Disable caching when inspecting so that mouse_hit_test has all hitboxes.
116            let caching_disabled = window.is_inspector_picking(cx);
117            match self.cached_style.as_ref() {
118                Some(style) if !caching_disabled => {
119                    let mut root_style = Style::default();
120                    root_style.refine(style);
121                    let layout_id = window.request_layout(root_style, None, cx);
122                    (layout_id, None)
123                }
124                _ => {
125                    let mut element = (self.render)(self, window, cx);
126                    let layout_id = element.request_layout(window, cx);
127                    (layout_id, Some(element))
128                }
129            }
130        })
131    }
132
133    fn prepaint(
134        &mut self,
135        global_id: Option<&GlobalElementId>,
136        _inspector_id: Option<&InspectorElementId>,
137        bounds: Bounds<Pixels>,
138        element: &mut Self::RequestLayoutState,
139        window: &mut Window,
140        cx: &mut App,
141    ) -> Option<AnyElement> {
142        window.set_view_id(self.entity_id());
143        window.with_rendered_view(self.entity_id(), |window| {
144            if let Some(mut element) = element.take() {
145                element.prepaint(window, cx);
146                return Some(element);
147            }
148
149            window.with_element_state::<AnyViewState, _>(
150                global_id.unwrap(),
151                |element_state, window| {
152                    let content_mask = window.content_mask();
153                    let text_style = window.text_style();
154
155                    if let Some(mut element_state) = element_state
156                        && element_state.cache_key.bounds == bounds
157                        && element_state.cache_key.content_mask == content_mask
158                        && element_state.cache_key.text_style == text_style
159                        && !window.dirty_views.contains(&self.entity_id())
160                        && !window.refreshing
161                    {
162                        let prepaint_start = window.prepaint_index();
163                        window.reuse_prepaint(element_state.prepaint_range.clone());
164                        cx.entities
165                            .extend_accessed(&element_state.accessed_entities);
166                        let prepaint_end = window.prepaint_index();
167                        element_state.prepaint_range = prepaint_start..prepaint_end;
168
169                        return (None, element_state);
170                    }
171
172                    let refreshing = mem::replace(&mut window.refreshing, true);
173                    let prepaint_start = window.prepaint_index();
174                    let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
175                        let mut element = (self.render)(self, window, cx);
176                        element.layout_as_root(bounds.size.into(), window, cx);
177                        element.prepaint_at(bounds.origin, window, cx);
178                        element
179                    });
180
181                    let prepaint_end = window.prepaint_index();
182                    window.refreshing = refreshing;
183
184                    (
185                        Some(element),
186                        AnyViewState {
187                            accessed_entities,
188                            prepaint_range: prepaint_start..prepaint_end,
189                            paint_range: PaintIndex::default()..PaintIndex::default(),
190                            cache_key: ViewCacheKey {
191                                bounds,
192                                content_mask,
193                                text_style,
194                            },
195                        },
196                    )
197                },
198            )
199        })
200    }
201
202    fn paint(
203        &mut self,
204        global_id: Option<&GlobalElementId>,
205        _inspector_id: Option<&InspectorElementId>,
206        _bounds: Bounds<Pixels>,
207        _: &mut Self::RequestLayoutState,
208        element: &mut Self::PrepaintState,
209        window: &mut Window,
210        cx: &mut App,
211    ) {
212        window.with_rendered_view(self.entity_id(), |window| {
213            let caching_disabled = window.is_inspector_picking(cx);
214            if self.cached_style.is_some() && !caching_disabled {
215                window.with_element_state::<AnyViewState, _>(
216                    global_id.unwrap(),
217                    |element_state, window| {
218                        let mut element_state = element_state.unwrap();
219
220                        let paint_start = window.paint_index();
221
222                        if let Some(element) = element {
223                            let refreshing = mem::replace(&mut window.refreshing, true);
224                            element.paint(window, cx);
225                            window.refreshing = refreshing;
226                        } else {
227                            window.reuse_paint(element_state.paint_range.clone());
228                        }
229
230                        let paint_end = window.paint_index();
231                        element_state.paint_range = paint_start..paint_end;
232
233                        ((), element_state)
234                    },
235                )
236            } else {
237                element.as_mut().unwrap().paint(window, cx);
238            }
239        });
240    }
241}
242
243impl<V: 'static + Render> IntoElement for Entity<V> {
244    type Element = AnyView;
245
246    fn into_element(self) -> Self::Element {
247        self.into()
248    }
249}
250
251impl IntoElement for AnyView {
252    type Element = Self;
253
254    fn into_element(self) -> Self::Element {
255        self
256    }
257}
258
259/// A weak, dynamically-typed view handle that does not prevent the view from being released.
260pub struct AnyWeakView {
261    entity: AnyWeakEntity,
262    render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
263}
264
265impl AnyWeakView {
266    /// Convert to a strongly-typed handle if the referenced view has not yet been released.
267    pub fn upgrade(&self) -> Option<AnyView> {
268        let entity = self.entity.upgrade()?;
269        Some(AnyView {
270            entity,
271            render: self.render,
272            cached_style: None,
273        })
274    }
275}
276
277impl<V: 'static + Render> From<WeakEntity<V>> for AnyWeakView {
278    fn from(view: WeakEntity<V>) -> Self {
279        AnyWeakView {
280            entity: view.into(),
281            render: any_view::render::<V>,
282        }
283    }
284}
285
286impl PartialEq for AnyWeakView {
287    fn eq(&self, other: &Self) -> bool {
288        self.entity == other.entity
289    }
290}
291
292impl std::fmt::Debug for AnyWeakView {
293    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294        f.debug_struct("AnyWeakView")
295            .field("entity_id", &self.entity.entity_id)
296            .finish_non_exhaustive()
297    }
298}
299
300mod any_view {
301    use crate::{AnyElement, AnyView, App, IntoElement, Render, Window};
302
303    pub(crate) fn render<V: 'static + Render>(
304        view: &AnyView,
305        window: &mut Window,
306        cx: &mut App,
307    ) -> AnyElement {
308        let view = view.clone().downcast::<V>().unwrap();
309        view.update(cx, |view, cx| view.render(window, cx).into_any_element())
310    }
311}
312
313/// A view that renders nothing
314pub struct EmptyView;
315
316impl Render for EmptyView {
317    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
318        Empty
319    }
320}