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