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