1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
use crate::{
AnyElement, AnyEntity, AnyWeakEntity, App, Bounds, ContentMask, Context, Element, ElementId,
Entity, EntityId, GlobalElementId, InspectorElementId, IntoElement, LayoutId, PaintIndex,
Pixels, PrepaintStateIndex, Render, RenderOnce, Style, StyleRefinement, TextStyle, WeakEntity,
};
use crate::{Empty, Window};
use anyhow::Result;
use collections::FxHashSet;
use refineable::Refineable;
use std::mem;
use std::{any::TypeId, fmt, ops::Range};
/// A dynamically-typed view handle that can be downcast to a specific `Entity<V>`.
///
/// This is the type-erased counterpart to [`ViewElement`]: it holds an entity plus
/// a function pointer to its render, and is itself a [`View`], so embedding it as an
/// element goes through the same [`ViewElement`] machinery as any other view.
#[derive(Clone, Debug)]
pub struct AnyView {
entity: AnyEntity,
render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
}
impl<V: Render> From<Entity<V>> for AnyView {
fn from(value: Entity<V>) -> Self {
AnyView {
entity: value.into_any(),
render: any_view::render::<V>,
}
}
}
impl AnyView {
/// Embed this view as a cached [`ViewElement`] laid out at `style`.
///
/// The rendered subtree is recycled from the previous frame unless
/// [Context::notify] was called on the backing entity since it was rendered
/// (or [Window::refresh] is called, which ignores caching).
pub fn cached(self, style: StyleRefinement) -> ViewElement<AnyView> {
ViewElement::new(self).cached(style)
}
/// Convert this to a weak handle.
pub fn downgrade(&self) -> AnyWeakView {
AnyWeakView {
entity: self.entity.downgrade(),
render: self.render,
}
}
/// Convert this to a [Entity] of a specific type.
/// If this handle does not contain a view of the specified type, returns itself in an `Err` variant.
pub fn downcast<T: 'static>(self) -> Result<Entity<T>, Self> {
match self.entity.downcast() {
Ok(entity) => Ok(entity),
Err(entity) => Err(Self {
entity,
render: self.render,
}),
}
}
/// Gets the [TypeId] of the underlying view.
pub fn entity_type(&self) -> TypeId {
self.entity.entity_type
}
/// The [`EntityId`] of this view.
pub fn entity_id(&self) -> EntityId {
self.entity.entity_id()
}
}
impl PartialEq for AnyView {
fn eq(&self, other: &Self) -> bool {
self.entity == other.entity
}
}
impl Eq for AnyView {}
/// `AnyView` is the type-erased [`View`]: its `render` is a function pointer rather
/// than a concrete type, but it participates in the reactive graph exactly like any
/// other view via [`ViewElement`].
impl View for AnyView {
fn entity_id(&self) -> Option<EntityId> {
Some(self.entity.entity_id())
}
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
(self.render)(&self, window, cx)
}
}
impl<V: 'static + Render> IntoElement for Entity<V> {
type Element = ViewElement<Entity<V>>;
fn into_element(self) -> Self::Element {
ViewElement::new(self)
}
}
impl IntoElement for AnyView {
type Element = ViewElement<AnyView>;
fn into_element(self) -> Self::Element {
ViewElement::new(self)
}
}
/// A weak, dynamically-typed view handle.
pub struct AnyWeakView {
entity: AnyWeakEntity,
render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
}
impl AnyWeakView {
/// Upgrade to a strong `AnyView` handle, if the view is still alive.
pub fn upgrade(&self) -> Option<AnyView> {
let entity = self.entity.upgrade()?;
Some(AnyView {
entity,
render: self.render,
})
}
}
impl<V: 'static + Render> From<WeakEntity<V>> for AnyWeakView {
fn from(view: WeakEntity<V>) -> Self {
AnyWeakView {
entity: view.into(),
render: any_view::render::<V>,
}
}
}
impl PartialEq for AnyWeakView {
fn eq(&self, other: &Self) -> bool {
self.entity == other.entity
}
}
impl std::fmt::Debug for AnyWeakView {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AnyWeakView")
.field("entity_id", &self.entity.entity_id)
.finish_non_exhaustive()
}
}
mod any_view {
use crate::{AnyElement, AnyView, App, IntoElement, Render, Window};
pub(crate) fn render<V: 'static + Render>(
view: &AnyView,
window: &mut Window,
cx: &mut App,
) -> AnyElement {
let view = view.clone().downcast::<V>().unwrap();
// Record the view's Render type name so the accessibility debug dump can
// attribute nodes to the view that produced them.
#[cfg(debug_assertions)]
window
.a11y
.view_type_names
.insert(view.entity_id(), std::any::type_name::<V>());
view.update(cx, |view, cx| view.render(window, cx).into_any_element())
}
}
/// A renderable that participates in GPUI's reactive graph — the unifying model
/// behind [`Render`] and [`RenderOnce`].
///
/// When `entity_id()` returns `Some`, that id becomes the view's identity: it gets
/// a unique element-id space (so internal `use_state` / `.id(..)` never collide
/// across siblings) and `cx.notify()` on that entity re-renders only this view's
/// subtree. `None` behaves like a stateless component.
///
/// You rarely implement `View` directly. `Entity<T: Render>` and any `T: RenderOnce`
/// get a blanket impl below; implement it by hand only when a component needs both
/// parent-supplied props *and* a backing entity for identity.
pub trait View: 'static + Sized {
/// This view's identity, if it has one. A view typically holds the backing
/// entity as a field and returns its [`EntityId`] here.
///
/// The id becomes this view's [`ElementId`], so two views keyed on the same
/// entity must not be rendered at the same position in the element tree
/// (e.g. as siblings under the same parent): their internal element state
/// (`use_state`, scroll offsets, etc.) would silently collide. Nesting is
/// fine — the id is scoped by the parent path.
fn entity_id(&self) -> Option<EntityId>;
/// Render this view into an element tree, consuming `self`.
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement;
}
/// A stateless component (`RenderOnce`) is a `View` with no identity.
impl<T: RenderOnce> View for T {
fn entity_id(&self) -> Option<EntityId> {
None
}
#[inline]
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
RenderOnce::render(self, window, cx)
}
}
/// An entity that renders itself (`Render`) is a `View` keyed on its own id.
impl<T: Render> View for Entity<T> {
fn entity_id(&self) -> Option<EntityId> {
Some(Entity::entity_id(self))
}
#[inline]
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
self.update(cx, |this, cx| {
Render::render(this, window, cx).into_any_element()
})
}
}
impl<T: Render> Entity<T> {
/// Embed this entity as a cached [`ViewElement`] laid out at `style`.
///
/// The rendered subtree is reused until the entity is notified (or the
/// cached bounds / text style change). Caching requires a definite size:
/// a cached view is laid out from `style` and is *not* measured from its
/// contents. Use [`ViewElement::new`] (or `.child(entity)`) for the
/// uncached case.
#[track_caller]
pub fn cached(self, style: StyleRefinement) -> ViewElement<Entity<T>> {
ViewElement::new(self).cached(style)
}
}
/// The element type for [`View`] implementations. Wraps a `View` and hooks it
/// into layout, prepaint, and paint. Constructed via [`ViewElement::new`].
#[doc(hidden)]
pub struct ViewElement<V: View> {
view: Option<V>,
entity_id: Option<EntityId>,
cached_style: Option<StyleRefinement>,
#[cfg(debug_assertions)]
source: &'static core::panic::Location<'static>,
}
impl<V: View> ViewElement<V> {
/// Wrap a [`View`] as an element.
#[track_caller]
pub fn new(view: V) -> Self {
let entity_id = view.entity_id();
ViewElement {
entity_id,
cached_style: None,
view: Some(view),
#[cfg(debug_assertions)]
source: core::panic::Location::caller(),
}
}
/// Enable caching of this view's rendered subtree, laid out at `style`.
/// The composer supplies the layout style because caching skips rendering
/// the contents to measure them.
///
/// Crate-private on purpose: caching is only sound for entity-backed views,
/// where [`Context::notify`] is the contract that busts the cache. A stateless
/// view has no such contract, so a frozen subtree could never be invalidated.
/// Reach this through [`Entity::cached`] or [`AnyView::cached`], which are
/// entity-backed by construction.
pub(crate) fn cached(mut self, style: StyleRefinement) -> Self {
self.cached_style = Some(style);
self
}
}
impl<V: View> IntoElement for ViewElement<V> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
struct ViewElementState {
prepaint_range: Range<PrepaintStateIndex>,
paint_range: Range<PaintIndex>,
cache_key: ViewElementCacheKey,
accessed_entities: FxHashSet<EntityId>,
}
struct ViewElementCacheKey {
bounds: Bounds<Pixels>,
content_mask: ContentMask<Pixels>,
text_style: TextStyle,
}
impl<V: View> Element for ViewElement<V> {
type RequestLayoutState = Option<AnyElement>;
type PrepaintState = Option<AnyElement>;
fn id(&self) -> Option<ElementId> {
self.entity_id.map(ElementId::View)
}
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
#[cfg(debug_assertions)]
return Some(self.source);
#[cfg(not(debug_assertions))]
return None;
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
if let Some(entity_id) = self.entity_id {
// Stateful path: create a reactive boundary.
window.with_rendered_view(entity_id, |window| {
let caching_disabled = window.is_inspector_picking(cx);
match self.cached_style.as_ref() {
Some(style) if !caching_disabled => {
let mut root_style = Style::default();
root_style.refine(style);
let layout_id = window.request_layout(root_style, None, cx);
(layout_id, None)
}
_ => {
let mut element = self
.view
.take()
.unwrap()
.render(window, cx)
.into_any_element();
let layout_id = element.request_layout(window, cx);
(layout_id, Some(element))
}
}
})
} else {
// Stateless path: isolate subtree via type name (no entity identity).
window.with_id(
ElementId::Name(std::any::type_name::<V>().into()),
|window| {
let mut element = self
.view
.take()
.unwrap()
.render(window, cx)
.into_any_element();
let layout_id = element.request_layout(window, cx);
(layout_id, Some(element))
},
)
}
}
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) -> Option<AnyElement> {
if let Some(entity_id) = self.entity_id {
// Stateful path.
window.set_view_id(entity_id);
window.with_rendered_view(entity_id, |window| {
if let Some(mut element) = element.take() {
element.prepaint(window, cx);
return Some(element);
}
window.with_element_state::<ViewElementState, _>(
global_id.unwrap(),
|element_state, window| {
let content_mask = window.content_mask();
let text_style = window.text_style();
if let Some(mut element_state) = element_state
&& element_state.cache_key.bounds == bounds
&& element_state.cache_key.content_mask == content_mask
&& element_state.cache_key.text_style == text_style
&& !window.dirty_views.contains(&entity_id)
&& !window.refreshing
{
let prepaint_start = window.prepaint_index();
window.reuse_prepaint(element_state.prepaint_range.clone());
cx.entities
.extend_accessed(&element_state.accessed_entities);
let prepaint_end = window.prepaint_index();
element_state.prepaint_range = prepaint_start..prepaint_end;
return (None, element_state);
}
let refreshing = mem::replace(&mut window.refreshing, true);
let prepaint_start = window.prepaint_index();
let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
let mut element = self
.view
.take()
.unwrap()
.render(window, cx)
.into_any_element();
element.layout_as_root(bounds.size.into(), window, cx);
element.prepaint_at(bounds.origin, window, cx);
element
});
let prepaint_end = window.prepaint_index();
window.refreshing = refreshing;
(
Some(element),
ViewElementState {
accessed_entities,
prepaint_range: prepaint_start..prepaint_end,
paint_range: PaintIndex::default()..PaintIndex::default(),
cache_key: ViewElementCacheKey {
bounds,
content_mask,
text_style,
},
},
)
},
)
})
} else {
// Stateless path: just prepaint the element.
window.with_id(
ElementId::Name(std::any::type_name::<V>().into()),
|window| {
element.as_mut().unwrap().prepaint(window, cx);
},
);
Some(element.take().unwrap())
}
}
fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
element: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
if let Some(entity_id) = self.entity_id {
// Stateful path.
window.with_rendered_view(entity_id, |window| {
let caching_disabled = window.is_inspector_picking(cx);
if self.cached_style.is_some() && !caching_disabled {
window.with_element_state::<ViewElementState, _>(
global_id.unwrap(),
|element_state, window| {
let mut element_state = element_state.unwrap();
let paint_start = window.paint_index();
if let Some(element) = element {
let refreshing = mem::replace(&mut window.refreshing, true);
element.paint(window, cx);
window.refreshing = refreshing;
} else {
window.reuse_paint(element_state.paint_range.clone());
}
let paint_end = window.paint_index();
element_state.paint_range = paint_start..paint_end;
((), element_state)
},
)
} else {
element.as_mut().unwrap().paint(window, cx);
}
});
} else {
// Stateless path: just paint the element.
window.with_id(
ElementId::Name(std::any::type_name::<V>().into()),
|window| {
element.as_mut().unwrap().paint(window, cx);
},
);
}
}
}
/// A view that renders nothing
pub struct EmptyView;
impl Render for EmptyView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Empty
}
}