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#[derive(Clone, Debug)]
23pub struct AnyView {
24 entity: AnyEntity,
25 render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
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 }
34 }
35}
36
37impl AnyView {
38 pub fn cached(self, style: StyleRefinement) -> ViewElement<AnyView> {
44 ViewElement::new(self).cached(style)
45 }
46
47 pub fn downgrade(&self) -> AnyWeakView {
49 AnyWeakView {
50 entity: self.entity.downgrade(),
51 render: self.render,
52 }
53 }
54
55 pub fn downcast<T: 'static>(self) -> Result<Entity<T>, Self> {
58 match self.entity.downcast() {
59 Ok(entity) => Ok(entity),
60 Err(entity) => Err(Self {
61 entity,
62 render: self.render,
63 }),
64 }
65 }
66
67 pub fn entity_type(&self) -> TypeId {
69 self.entity.entity_type
70 }
71
72 pub fn entity_id(&self) -> EntityId {
74 self.entity.entity_id()
75 }
76}
77
78impl PartialEq for AnyView {
79 fn eq(&self, other: &Self) -> bool {
80 self.entity == other.entity
81 }
82}
83
84impl Eq for AnyView {}
85
86impl View for AnyView {
90 fn entity_id(&self) -> Option<EntityId> {
91 Some(self.entity.entity_id())
92 }
93
94 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
95 (self.render)(&self, window, cx)
96 }
97}
98
99impl<V: 'static + Render> IntoElement for Entity<V> {
100 type Element = ViewElement<Entity<V>>;
101
102 fn into_element(self) -> Self::Element {
103 ViewElement::new(self)
104 }
105
106 #[inline(never)]
107 fn into_any_element(self) -> AnyElement {
108 self.into_element().into_any()
109 }
110}
111
112impl IntoElement for AnyView {
113 type Element = ViewElement<AnyView>;
114
115 fn into_element(self) -> Self::Element {
116 ViewElement::new(self)
117 }
118}
119
120pub struct AnyWeakView {
122 entity: AnyWeakEntity,
123 render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
124}
125
126impl AnyWeakView {
127 pub fn upgrade(&self) -> Option<AnyView> {
129 let entity = self.entity.upgrade()?;
130 Some(AnyView {
131 entity,
132 render: self.render,
133 })
134 }
135}
136
137impl<V: 'static + Render> From<WeakEntity<V>> for AnyWeakView {
138 fn from(view: WeakEntity<V>) -> Self {
139 AnyWeakView {
140 entity: view.into(),
141 render: any_view::render::<V>,
142 }
143 }
144}
145
146impl PartialEq for AnyWeakView {
147 fn eq(&self, other: &Self) -> bool {
148 self.entity == other.entity
149 }
150}
151
152impl std::fmt::Debug for AnyWeakView {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 f.debug_struct("AnyWeakView")
155 .field("entity_id", &self.entity.entity_id)
156 .finish_non_exhaustive()
157 }
158}
159
160mod any_view {
161 use crate::{AnyElement, AnyView, App, IntoElement, Render, Window};
162
163 pub(crate) fn render<V: 'static + Render>(
164 view: &AnyView,
165 window: &mut Window,
166 cx: &mut App,
167 ) -> AnyElement {
168 let view = view.clone().downcast::<V>().unwrap();
169 #[cfg(debug_assertions)]
172 window
173 .core
174 .a11y
175 .view_type_names
176 .insert(view.entity_id(), std::any::type_name::<V>());
177 view.update(cx, |view, cx| view.render(window, cx).into_any_element())
178 }
179}
180
181pub trait View: 'static + Sized {
193 fn entity_id(&self) -> Option<EntityId>;
202
203 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement;
205}
206
207impl<T: RenderOnce> View for T {
209 fn entity_id(&self) -> Option<EntityId> {
210 None
211 }
212
213 #[inline]
214 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
215 RenderOnce::render(self, window, cx)
216 }
217}
218
219impl<T: Render> View for Entity<T> {
221 fn entity_id(&self) -> Option<EntityId> {
222 Some(Entity::entity_id(self))
223 }
224
225 #[inline]
226 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
227 self.update(cx, |this, cx| {
228 Render::render(this, window, cx).into_any_element()
229 })
230 }
231}
232
233impl<T: Render> Entity<T> {
234 #[track_caller]
242 pub fn cached(self, style: StyleRefinement) -> ViewElement<Entity<T>> {
243 ViewElement::new(self).cached(style)
244 }
245}
246
247#[doc(hidden)]
250pub struct ViewElement<V: View> {
251 view: Option<V>,
252 entity_id: Option<EntityId>,
253 cached_style: Option<StyleRefinement>,
254 #[cfg(debug_assertions)]
255 source: &'static core::panic::Location<'static>,
256}
257
258impl<V: View> ViewElement<V> {
259 #[track_caller]
261 pub fn new(view: V) -> Self {
262 let entity_id = view.entity_id();
263 ViewElement {
264 entity_id,
265 cached_style: None,
266 view: Some(view),
267 #[cfg(debug_assertions)]
268 source: core::panic::Location::caller(),
269 }
270 }
271
272 pub(crate) fn cached(mut self, style: StyleRefinement) -> Self {
282 self.cached_style = Some(style);
283 self
284 }
285}
286
287impl<V: View> IntoElement for ViewElement<V> {
288 type Element = Self;
289
290 fn into_element(self) -> Self::Element {
291 self
292 }
293}
294
295pub(crate) struct ViewElementState {
296 prepaint_range: Range<PrepaintStateIndex>,
297 paint_range: Range<PaintIndex>,
298 cache_key: ViewElementCacheKey,
299 accessed_entities: FxHashSet<EntityId>,
300}
301
302pub(crate) struct ViewElementCacheKey {
303 bounds: Bounds<Pixels>,
304 content_mask: ContentMask<Pixels>,
305 text_style: TextStyle,
306}
307
308impl<V: View> Element for ViewElement<V> {
309 type RequestLayoutState = Option<AnyElement>;
310 type PrepaintState = Option<AnyElement>;
311
312 fn id(&self) -> Option<ElementId> {
313 self.entity_id.map(ElementId::View)
314 }
315
316 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
317 #[cfg(debug_assertions)]
318 return Some(self.source);
319
320 #[cfg(not(debug_assertions))]
321 return None;
322 }
323
324 fn request_layout(
325 &mut self,
326 _id: Option<&GlobalElementId>,
327 window: &mut Window,
328 cx: &mut App,
329 ) -> (LayoutId, Self::RequestLayoutState) {
330 if let Some(entity_id) = self.entity_id {
331 let view = &mut self.view;
333 request_layout_view(
334 entity_id,
335 self.cached_style.as_ref(),
336 window,
337 cx,
338 &mut |window, cx| view.take().unwrap().render(window, cx).into_any_element(),
339 )
340 } else {
341 request_layout_component(type_name::<V>(), window, cx, &mut |window, cx| {
343 self.view
344 .take()
345 .unwrap()
346 .render(window, cx)
347 .into_any_element()
348 })
349 }
350 }
351
352 fn prepaint(
353 &mut self,
354 global_id: Option<&GlobalElementId>,
355 bounds: Bounds<Pixels>,
356 element: &mut Self::RequestLayoutState,
357 window: &mut Window,
358 cx: &mut App,
359 ) -> Option<AnyElement> {
360 if let Some(entity_id) = self.entity_id {
361 window.set_view_id(entity_id);
363 window.with_rendered_view(entity_id, |window| {
364 if let Some(mut element) = element.take() {
365 element.prepaint(window, cx);
366 return Some(element);
367 }
368
369 prepaint_cached_view(entity_id, global_id, bounds, window, cx, |window, cx| {
370 self.view
371 .take()
372 .unwrap()
373 .render(window, cx)
374 .into_any_element()
375 })
376 })
377 } else {
378 prepaint_component(type_name::<V>(), element, window, cx)
380 }
381 }
382
383 fn paint(
384 &mut self,
385 global_id: Option<&GlobalElementId>,
386 _bounds: Bounds<Pixels>,
387 _request_layout: &mut Self::RequestLayoutState,
388 element: &mut Self::PrepaintState,
389 window: &mut Window,
390 cx: &mut App,
391 ) {
392 if let Some(entity_id) = self.entity_id {
393 paint_view(
395 entity_id,
396 self.cached_style.is_some(),
397 global_id,
398 element,
399 window,
400 cx,
401 );
402 } else {
403 paint_component(std::any::type_name::<V>(), element, window, cx);
405 }
406 }
407}
408
409pub struct EmptyView;
411
412impl Render for EmptyView {
413 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
414 Empty
415 }
416}
417
418pub(crate) fn prepaint_cached_view(
427 entity_id: EntityId,
428 global_id: Option<&GlobalElementId>,
429 bounds: Bounds<Pixels>,
430 window: &mut Window,
431 cx: &mut App,
432 build: impl FnOnce(&mut Window, &mut App) -> AnyElement,
433) -> Option<AnyElement> {
434 window.with_element_state::<ViewElementState, _>(global_id.unwrap(), |element_state, window| {
435 let content_mask = window.content_mask();
436 let text_style = window.text_style();
437
438 if let Some(mut element_state) = element_state
439 && element_state.cache_key.bounds == bounds
440 && element_state.cache_key.content_mask == content_mask
441 && element_state.cache_key.text_style == text_style
442 && !window.frame_state.dirty_views.contains(&entity_id)
443 && !window.core.refreshing
444 {
445 let prepaint_start = window.prepaint_index();
446 window.reuse_prepaint(element_state.prepaint_range.clone());
447 cx.entities
448 .extend_accessed(&element_state.accessed_entities);
449 let prepaint_end = window.prepaint_index();
450 element_state.prepaint_range = prepaint_start..prepaint_end;
451
452 return (None, element_state);
453 }
454
455 let refreshing = mem::replace(&mut window.core.refreshing, true);
456 let prepaint_start = window.prepaint_index();
457 let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
458 let mut element = build(window, cx);
459 element.layout_as_root(bounds.size.into(), window, cx);
460 element.prepaint_at(bounds.origin, window, cx);
461 element
462 });
463
464 let prepaint_end = window.prepaint_index();
465 window.core.refreshing = refreshing;
466
467 (
468 Some(element),
469 ViewElementState {
470 accessed_entities,
471 prepaint_range: prepaint_start..prepaint_end,
472 paint_range: PaintIndex::default()..PaintIndex::default(),
473 cache_key: ViewElementCacheKey {
474 bounds,
475 content_mask,
476 text_style,
477 },
478 },
479 )
480 })
481}
482
483#[inline(never)]
484fn request_layout_view(
485 entity_id: EntityId,
486 cached_style: Option<&StyleRefinement>,
487 window: &mut Window,
488 cx: &mut App,
489 render: &mut dyn FnMut(&mut Window, &mut App) -> AnyElement,
490) -> (LayoutId, Option<AnyElement>) {
491 window.with_rendered_view(entity_id, |window| {
492 let caching_disabled = window.is_inspector_picking(cx);
493 match cached_style {
494 Some(style) if !caching_disabled => {
495 let mut root_style = Style::default();
496 root_style.refine(style);
497 let layout_id = window.request_layout(root_style, None, cx);
498 (layout_id, None)
499 }
500 _ => {
501 let mut element = render(window, cx);
502 let layout_id = element.request_layout(window, cx);
503 (layout_id, Some(element))
504 }
505 }
506 })
507}
508
509#[inline(never)]
510fn request_layout_component(
511 name: &'static str,
512 window: &mut Window,
513 cx: &mut App,
514 render: &mut dyn FnMut(&mut Window, &mut App) -> AnyElement,
515) -> (LayoutId, Option<AnyElement>) {
516 window.with_id(ElementId::from(name), |window| {
517 let mut element = render(window, cx);
518 let layout_id = element.request_layout(window, cx);
519 (layout_id, Some(element))
520 })
521}
522
523#[inline(never)]
524fn prepaint_component(
525 name: &'static str,
526 element: &mut Option<AnyElement>,
527 window: &mut Window,
528 cx: &mut App,
529) -> Option<AnyElement> {
530 window.with_id(ElementId::from(name), |window| {
531 element.as_mut().unwrap().prepaint(window, cx);
532 });
533 Some(element.take().unwrap())
534}
535
536#[inline(never)]
537pub(crate) fn paint_view(
538 entity_id: EntityId,
539 cached: bool,
540 global_id: Option<&GlobalElementId>,
541 element: &mut Option<AnyElement>,
542 window: &mut Window,
543 cx: &mut App,
544) {
545 window.with_rendered_view(entity_id, |window| {
546 let caching_disabled = window.is_inspector_picking(cx);
547 if cached && !caching_disabled {
548 window.with_element_state::<ViewElementState, _>(
549 global_id.unwrap(),
550 |element_state, window| {
551 let mut element_state = element_state.unwrap();
552
553 let paint_start = window.paint_index();
554
555 if let Some(element) = element {
556 let refreshing = mem::replace(&mut window.core.refreshing, true);
557 element.paint(window, cx);
558 window.core.refreshing = refreshing;
559 } else {
560 window.reuse_paint(element_state.paint_range.clone());
561 }
562
563 let paint_end = window.paint_index();
564 element_state.paint_range = paint_start..paint_end;
565
566 ((), element_state)
567 },
568 )
569 } else {
570 element.as_mut().unwrap().paint(window, cx);
571 }
572 });
573}
574
575#[inline(never)]
576fn paint_component(
577 name: &'static str,
578 element: &mut Option<AnyElement>,
579 window: &mut Window,
580 cx: &mut App,
581) {
582 window.with_id(ElementId::Name(name.into()), |window| {
583 element.as_mut().unwrap().paint(window, cx);
584 });
585}
586
587#[cfg(test)]
588mod tests {
589 use std::{cell::Cell, rc::Rc};
590
591 use crate::{
592 AnyWindowHandle, AppContext as _, Context, Entity, IntoElement, ParentElement as _, Render,
593 StyleRefinement, Styled as _, TestAppContext, Window, div, green, px, size,
594 };
595
596 struct CountedView {
599 renders: Rc<Cell<usize>>,
600 }
601
602 impl Render for CountedView {
603 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
604 self.renders.set(self.renders.get() + 1);
605 div().w(px(10.)).h(px(10.)).bg(green())
606 }
607 }
608
609 struct CachedViewHost {
610 view: Entity<CountedView>,
611 }
612
613 impl Render for CachedViewHost {
614 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
615 div().size_full().child(
616 self.view
617 .clone()
618 .cached(StyleRefinement::default().size_full()),
619 )
620 }
621 }
622
623 fn draw(cx: &mut TestAppContext, window: AnyWindowHandle) -> usize {
625 cx.update_window(window, |_, window, cx| {
626 window.draw(cx).clear(cx);
627 window.frame_state.rendered_frame.scene.quads.len()
628 })
629 .unwrap()
630 }
631
632 #[gpui::test]
633 fn a_cached_view_reuses_its_subtree_until_it_is_notified_or_resized(cx: &mut TestAppContext) {
634 let renders = Rc::new(Cell::new(0));
635 let view = cx.new({
636 let renders = renders.clone();
637 move |_| CountedView { renders }
638 });
639 let window: AnyWindowHandle = cx
640 .add_window({
641 let view = view.clone();
642 move |_, _| CachedViewHost { view }
643 })
644 .into();
645
646 let first = draw(cx, window);
647 assert_eq!(renders.get(), 1, "the first frame renders the view");
648 assert!(first > 0, "the view's content is painted");
649
650 let second = draw(cx, window);
651 assert_eq!(renders.get(), 1, "a clean cached view is not re-rendered");
652 assert_eq!(
653 second, first,
654 "the reused subtree is still painted into the scene"
655 );
656
657 view.update(cx, |_, cx| cx.notify());
658 draw(cx, window);
659 assert!(renders.get() > 1, "notifying the view re-renders it");
660
661 let renders_before_resize = renders.get();
663 cx.simulate_window_resize(window, size(px(400.), px(300.)));
664 draw(cx, window);
665 assert!(
666 renders.get() > renders_before_resize,
667 "resizing the window re-renders the cached view"
668 );
669 }
670}