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, Styled, 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, hash::Hash, 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 view handle that can be downcast to a specific `Entity<V>`.
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 /// Enable caching with the given outer layout style.
48 pub fn cached(mut self, style: StyleRefinement) -> Self {
49 self.cached_style = Some(style.into());
50 self
51 }
52
53 /// Downgrade to a weak handle.
54 pub fn downgrade(&self) -> AnyWeakView {
55 AnyWeakView {
56 entity: self.entity.downgrade(),
57 render: self.render,
58 }
59 }
60
61 /// Downcast to a typed `Entity<T>`, returning `Err(self)` on type mismatch.
62 pub fn downcast<T: 'static>(self) -> Result<Entity<T>, Self> {
63 match self.entity.downcast() {
64 Ok(entity) => Ok(entity),
65 Err(entity) => Err(Self {
66 entity,
67 render: self.render,
68 cached_style: self.cached_style,
69 }),
70 }
71 }
72
73 /// The [`TypeId`] of the underlying view.
74 pub fn entity_type(&self) -> TypeId {
75 self.entity.entity_type
76 }
77
78 /// The [`EntityId`] of this view.
79 pub fn entity_id(&self) -> EntityId {
80 self.entity.entity_id()
81 }
82}
83
84impl PartialEq for AnyView {
85 fn eq(&self, other: &Self) -> bool {
86 self.entity == other.entity
87 }
88}
89
90impl Eq for AnyView {}
91
92impl Element for AnyView {
93 type RequestLayoutState = Option<AnyElement>;
94 type PrepaintState = Option<AnyElement>;
95
96 fn id(&self) -> Option<ElementId> {
97 Some(ElementId::View(self.entity_id()))
98 }
99
100 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
101 None
102 }
103
104 fn request_layout(
105 &mut self,
106 _id: Option<&GlobalElementId>,
107 _inspector_id: Option<&InspectorElementId>,
108 window: &mut Window,
109 cx: &mut App,
110 ) -> (LayoutId, Self::RequestLayoutState) {
111 window.with_rendered_view(self.entity_id(), |window| {
112 // Disable caching when inspecting so that mouse_hit_test has all hitboxes.
113 let caching_disabled = window.is_inspector_picking(cx);
114 match self.cached_style.as_ref() {
115 Some(style) if !caching_disabled => {
116 let mut root_style = Style::default();
117 root_style.refine(style);
118 let layout_id = window.request_layout(root_style, None, cx);
119 (layout_id, None)
120 }
121 _ => {
122 let mut element = (self.render)(self, window, cx);
123 let layout_id = element.request_layout(window, cx);
124 (layout_id, Some(element))
125 }
126 }
127 })
128 }
129
130 fn prepaint(
131 &mut self,
132 global_id: Option<&GlobalElementId>,
133 _inspector_id: Option<&InspectorElementId>,
134 bounds: Bounds<Pixels>,
135 element: &mut Self::RequestLayoutState,
136 window: &mut Window,
137 cx: &mut App,
138 ) -> Option<AnyElement> {
139 window.set_view_id(self.entity_id());
140 window.with_rendered_view(self.entity_id(), |window| {
141 if let Some(mut element) = element.take() {
142 element.prepaint(window, cx);
143 return Some(element);
144 }
145
146 window.with_element_state::<AnyViewState, _>(
147 global_id.unwrap(),
148 |element_state, window| {
149 let content_mask = window.content_mask();
150 let text_style = window.text_style();
151
152 if let Some(mut element_state) = element_state
153 && element_state.cache_key.bounds == bounds
154 && element_state.cache_key.content_mask == content_mask
155 && element_state.cache_key.text_style == text_style
156 && !window.dirty_views.contains(&self.entity_id())
157 && !window.refreshing
158 {
159 let prepaint_start = window.prepaint_index();
160 window.reuse_prepaint(element_state.prepaint_range.clone());
161 cx.entities
162 .extend_accessed(&element_state.accessed_entities);
163 let prepaint_end = window.prepaint_index();
164 element_state.prepaint_range = prepaint_start..prepaint_end;
165
166 return (None, element_state);
167 }
168
169 let refreshing = mem::replace(&mut window.refreshing, true);
170 let prepaint_start = window.prepaint_index();
171 let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
172 let mut element = (self.render)(self, window, cx);
173 element.layout_as_root(bounds.size.into(), window, cx);
174 element.prepaint_at(bounds.origin, window, cx);
175 element
176 });
177
178 let prepaint_end = window.prepaint_index();
179 window.refreshing = refreshing;
180
181 (
182 Some(element),
183 AnyViewState {
184 accessed_entities,
185 prepaint_range: prepaint_start..prepaint_end,
186 paint_range: PaintIndex::default()..PaintIndex::default(),
187 cache_key: ViewCacheKey {
188 bounds,
189 content_mask,
190 text_style,
191 },
192 },
193 )
194 },
195 )
196 })
197 }
198
199 fn paint(
200 &mut self,
201 global_id: Option<&GlobalElementId>,
202 _inspector_id: Option<&InspectorElementId>,
203 _bounds: Bounds<Pixels>,
204 _: &mut Self::RequestLayoutState,
205 element: &mut Self::PrepaintState,
206 window: &mut Window,
207 cx: &mut App,
208 ) {
209 window.with_rendered_view(self.entity_id(), |window| {
210 let caching_disabled = window.is_inspector_picking(cx);
211 if self.cached_style.is_some() && !caching_disabled {
212 window.with_element_state::<AnyViewState, _>(
213 global_id.unwrap(),
214 |element_state, window| {
215 let mut element_state = element_state.unwrap();
216
217 let paint_start = window.paint_index();
218
219 if let Some(element) = element {
220 let refreshing = mem::replace(&mut window.refreshing, true);
221 element.paint(window, cx);
222 window.refreshing = refreshing;
223 } else {
224 window.reuse_paint(element_state.paint_range.clone());
225 }
226
227 let paint_end = window.paint_index();
228 element_state.paint_range = paint_start..paint_end;
229
230 ((), element_state)
231 },
232 )
233 } else {
234 element.as_mut().unwrap().paint(window, cx);
235 }
236 });
237 }
238}
239
240impl<V: 'static + Render> IntoElement for Entity<V> {
241 type Element = AnyView;
242
243 fn into_element(self) -> Self::Element {
244 self.into()
245 }
246}
247
248impl IntoElement for AnyView {
249 type Element = Self;
250
251 fn into_element(self) -> Self::Element {
252 self
253 }
254}
255
256/// A weak, dynamically-typed view handle.
257pub struct AnyWeakView {
258 entity: AnyWeakEntity,
259 render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
260}
261
262impl AnyWeakView {
263 /// Upgrade to a strong `AnyView` handle, if the view is still alive.
264 pub fn upgrade(&self) -> Option<AnyView> {
265 let entity = self.entity.upgrade()?;
266 Some(AnyView {
267 entity,
268 render: self.render,
269 cached_style: None,
270 })
271 }
272}
273
274impl<V: 'static + Render> From<WeakEntity<V>> for AnyWeakView {
275 fn from(view: WeakEntity<V>) -> Self {
276 AnyWeakView {
277 entity: view.into(),
278 render: any_view::render::<V>,
279 }
280 }
281}
282
283impl PartialEq for AnyWeakView {
284 fn eq(&self, other: &Self) -> bool {
285 self.entity == other.entity
286 }
287}
288
289impl std::fmt::Debug for AnyWeakView {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 f.debug_struct("AnyWeakView")
292 .field("entity_id", &self.entity.entity_id)
293 .finish_non_exhaustive()
294 }
295}
296
297mod any_view {
298 use crate::{AnyElement, AnyView, App, IntoElement, Render, Window};
299
300 pub(crate) fn render<V: 'static + Render>(
301 view: &AnyView,
302 window: &mut Window,
303 cx: &mut App,
304 ) -> AnyElement {
305 let view = view.clone().downcast::<V>().unwrap();
306 view.update(cx, |view, cx| view.render(window, cx).into_any_element())
307 }
308}
309
310/// A renderable component that participates in GPUI's reactive graph.
311///
312/// When `entity()` returns `Some`, `cx.notify()` on that entity only
313/// re-renders this view's subtree. Implement this trait directly for
314/// full control, or use [`ComponentView`] / [`EntityView`] for the
315/// common stateless / stateful cases.
316pub trait View: 'static + Sized + Hash {
317 /// The entity type that backs this view's state.
318 type Entity: 'static;
319
320 /// The entity that backs this view, if any. `Some` creates a reactive boundary;
321 /// `None` behaves like a stateless component.
322 fn entity(&self) -> Option<Entity<Self::Entity>>;
323
324 /// Render this view into an element tree, consuming `self`.
325 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement;
326
327 /// Outer layout style for caching. `Some` enables caching; `None` disables it.
328 /// Defaults to full-size (`width: 100%, height: 100%`).
329 fn cache_style(&mut self, _window: &mut Window, _cx: &mut App) -> Option<StyleRefinement> {
330 Some(StyleRefinement::default().size_full())
331 }
332}
333
334/// A stateless component that renders an element tree without an entity.
335/// This is the [`View`] equivalent of [`RenderOnce`](crate::RenderOnce).
336pub trait ComponentView: 'static + Sized + Hash {
337 /// Render this component into an element tree, consuming `self`.
338 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement;
339
340 /// Outer layout style for caching. Defaults to `None` (no caching).
341 fn cache_style(&mut self, _window: &mut Window, _cx: &mut App) -> Option<StyleRefinement> {
342 None
343 }
344}
345
346impl<T: ComponentView> View for T {
347 type Entity = ();
348
349 fn entity(&self) -> Option<Entity<()>> {
350 None
351 }
352
353 #[inline]
354 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
355 ComponentView::render(self, window, cx)
356 }
357
358 #[inline]
359 fn cache_style(&mut self, window: &mut Window, cx: &mut App) -> Option<StyleRefinement> {
360 ComponentView::cache_style(self, window, cx)
361 }
362}
363
364/// A stateful entity that renders an element tree with a reactive boundary.
365/// This is the [`View`]-based replacement for [`Render`].
366///
367/// Gives `Entity<T>` a blanket [`View`] impl. Use `ViewElement::new(entity)`
368/// to convert an `Entity<T>` into a child element.
369pub trait EntityView: 'static + Sized {
370 /// Render this entity into an element tree.
371 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement;
372
373 /// Outer layout style for caching. Defaults to `None` (no caching).
374 fn cache_style(
375 &mut self,
376 _window: &mut Window,
377 _cx: &mut Context<Self>,
378 ) -> Option<StyleRefinement> {
379 None
380 }
381}
382
383impl<T: EntityView> View for Entity<T> {
384 type Entity = T;
385
386 fn entity(&self) -> Option<Entity<Self::Entity>> {
387 Some(self.clone())
388 }
389
390 #[inline]
391 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
392 self.update(cx, |this, cx| {
393 EntityView::render(this, window, cx).into_any_element()
394 })
395 }
396
397 #[inline]
398 fn cache_style(&mut self, window: &mut Window, cx: &mut App) -> Option<StyleRefinement> {
399 self.update(cx, |this, cx| EntityView::cache_style(this, window, cx))
400 }
401}
402
403/// The element type for [`View`] implementations. Wraps a `View` and hooks
404/// it into. Constructed via [`ViewElement::new`].
405#[doc(hidden)]
406pub struct ViewElement<V: View> {
407 view: Option<V>,
408 entity_id: Option<EntityId>,
409 props_hash: u64,
410 cached_style: Option<StyleRefinement>,
411 #[cfg(debug_assertions)]
412 source: &'static core::panic::Location<'static>,
413}
414
415impl<V: View> ViewElement<V> {
416 /// Wrap a [`View`] as an element.
417 #[track_caller]
418 pub fn new(view: V) -> Self {
419 use std::hash::Hasher;
420 let entity_id = view.entity().map(|e| e.entity_id());
421 let mut hasher = std::collections::hash_map::DefaultHasher::new();
422 view.hash(&mut hasher);
423 let props_hash = hasher.finish();
424 ViewElement {
425 entity_id,
426 props_hash,
427 cached_style: None,
428 view: Some(view),
429 #[cfg(debug_assertions)]
430 source: core::panic::Location::caller(),
431 }
432 }
433}
434
435impl<V: View> IntoElement for ViewElement<V> {
436 type Element = Self;
437
438 fn into_element(self) -> Self::Element {
439 self
440 }
441}
442
443struct ViewElementState {
444 prepaint_range: Range<PrepaintStateIndex>,
445 paint_range: Range<PaintIndex>,
446 cache_key: ViewElementCacheKey,
447 accessed_entities: FxHashSet<EntityId>,
448}
449
450struct ViewElementCacheKey {
451 bounds: Bounds<Pixels>,
452 content_mask: ContentMask<Pixels>,
453 text_style: TextStyle,
454 props_hash: u64,
455}
456
457impl<V: View> Element for ViewElement<V> {
458 type RequestLayoutState = Option<AnyElement>;
459 type PrepaintState = Option<AnyElement>;
460
461 fn id(&self) -> Option<ElementId> {
462 self.entity_id.map(ElementId::View)
463 }
464
465 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
466 #[cfg(debug_assertions)]
467 return Some(self.source);
468
469 #[cfg(not(debug_assertions))]
470 return None;
471 }
472
473 fn request_layout(
474 &mut self,
475 _id: Option<&GlobalElementId>,
476 _inspector_id: Option<&InspectorElementId>,
477 window: &mut Window,
478 cx: &mut App,
479 ) -> (LayoutId, Self::RequestLayoutState) {
480 if self.cached_style.is_none() {
481 if let Some(view) = self.view.as_mut() {
482 self.cached_style = view.cache_style(window, cx);
483 }
484 }
485
486 if let Some(entity_id) = self.entity_id {
487 // Stateful path: create a reactive boundary.
488 window.with_rendered_view(entity_id, |window| {
489 let caching_disabled = window.is_inspector_picking(cx);
490 match self.cached_style.as_ref() {
491 Some(style) if !caching_disabled => {
492 let mut root_style = Style::default();
493 root_style.refine(style);
494 let layout_id = window.request_layout(root_style, None, cx);
495 (layout_id, None)
496 }
497 _ => {
498 let mut element = self
499 .view
500 .take()
501 .unwrap()
502 .render(window, cx)
503 .into_any_element();
504 let layout_id = element.request_layout(window, cx);
505 (layout_id, Some(element))
506 }
507 }
508 })
509 } else {
510 // Stateless path: isolate subtree via type name (like Component<C>).
511 window.with_id(
512 ElementId::Name(std::any::type_name::<V>().into()),
513 |window| {
514 let mut element = self
515 .view
516 .take()
517 .unwrap()
518 .render(window, cx)
519 .into_any_element();
520 let layout_id = element.request_layout(window, cx);
521 (layout_id, Some(element))
522 },
523 )
524 }
525 }
526
527 fn prepaint(
528 &mut self,
529 global_id: Option<&GlobalElementId>,
530 _inspector_id: Option<&InspectorElementId>,
531 bounds: Bounds<Pixels>,
532 element: &mut Self::RequestLayoutState,
533 window: &mut Window,
534 cx: &mut App,
535 ) -> Option<AnyElement> {
536 if let Some(entity_id) = self.entity_id {
537 // Stateful path.
538 window.set_view_id(entity_id);
539 window.with_rendered_view(entity_id, |window| {
540 if let Some(mut element) = element.take() {
541 element.prepaint(window, cx);
542 return Some(element);
543 }
544
545 window.with_element_state::<ViewElementState, _>(
546 global_id.unwrap(),
547 |element_state, window| {
548 let content_mask = window.content_mask();
549 let text_style = window.text_style();
550
551 if let Some(mut element_state) = element_state
552 && element_state.cache_key.bounds == bounds
553 && element_state.cache_key.content_mask == content_mask
554 && element_state.cache_key.text_style == text_style
555 && element_state.cache_key.props_hash == self.props_hash
556 && !window.dirty_views.contains(&entity_id)
557 && !window.refreshing
558 {
559 let prepaint_start = window.prepaint_index();
560 window.reuse_prepaint(element_state.prepaint_range.clone());
561 cx.entities
562 .extend_accessed(&element_state.accessed_entities);
563 let prepaint_end = window.prepaint_index();
564 element_state.prepaint_range = prepaint_start..prepaint_end;
565
566 return (None, element_state);
567 }
568
569 let refreshing = mem::replace(&mut window.refreshing, true);
570 let prepaint_start = window.prepaint_index();
571 let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
572 let mut element = self
573 .view
574 .take()
575 .unwrap()
576 .render(window, cx)
577 .into_any_element();
578 element.layout_as_root(bounds.size.into(), window, cx);
579 element.prepaint_at(bounds.origin, window, cx);
580 element
581 });
582
583 let prepaint_end = window.prepaint_index();
584 window.refreshing = refreshing;
585
586 (
587 Some(element),
588 ViewElementState {
589 accessed_entities,
590 prepaint_range: prepaint_start..prepaint_end,
591 paint_range: PaintIndex::default()..PaintIndex::default(),
592 cache_key: ViewElementCacheKey {
593 bounds,
594 content_mask,
595 text_style,
596 props_hash: self.props_hash,
597 },
598 },
599 )
600 },
601 )
602 })
603 } else {
604 // Stateless path: just prepaint the element.
605 window.with_id(
606 ElementId::Name(std::any::type_name::<V>().into()),
607 |window| {
608 element.as_mut().unwrap().prepaint(window, cx);
609 },
610 );
611 Some(element.take().unwrap())
612 }
613 }
614
615 fn paint(
616 &mut self,
617 global_id: Option<&GlobalElementId>,
618 _inspector_id: Option<&InspectorElementId>,
619 _bounds: Bounds<Pixels>,
620 _request_layout: &mut Self::RequestLayoutState,
621 element: &mut Self::PrepaintState,
622 window: &mut Window,
623 cx: &mut App,
624 ) {
625 if let Some(entity_id) = self.entity_id {
626 // Stateful path.
627 window.with_rendered_view(entity_id, |window| {
628 let caching_disabled = window.is_inspector_picking(cx);
629 if self.cached_style.is_some() && !caching_disabled {
630 window.with_element_state::<ViewElementState, _>(
631 global_id.unwrap(),
632 |element_state, window| {
633 let mut element_state = element_state.unwrap();
634
635 let paint_start = window.paint_index();
636
637 if let Some(element) = element {
638 let refreshing = mem::replace(&mut window.refreshing, true);
639 element.paint(window, cx);
640 window.refreshing = refreshing;
641 } else {
642 window.reuse_paint(element_state.paint_range.clone());
643 }
644
645 let paint_end = window.paint_index();
646 element_state.paint_range = paint_start..paint_end;
647
648 ((), element_state)
649 },
650 )
651 } else {
652 element.as_mut().unwrap().paint(window, cx);
653 }
654 });
655 } else {
656 // Stateless path: just paint the element.
657 window.with_id(
658 ElementId::Name(std::any::type_name::<V>().into()),
659 |window| {
660 element.as_mut().unwrap().paint(window, cx);
661 },
662 );
663 }
664 }
665}
666
667/// A view that renders nothing
668pub struct EmptyView;
669
670impl Render for EmptyView {
671 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
672 Empty
673 }
674}