Skip to repository content508 lines · 18.2 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:35:08.714Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
view.rs
1use crate::{
2 AnyElement, AnyEntity, AnyWeakEntity, App, Bounds, ContentMask, Context, Element, ElementId,
3 Entity, EntityId, GlobalElementId, InspectorElementId, IntoElement, LayoutId, PaintIndex,
4 Pixels, 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/// A dynamically-typed view handle that can be downcast to a specific `Entity<V>`.
14///
15/// This is the type-erased counterpart to [`ViewElement`]: it holds an entity plus
16/// a function pointer to its render, and is itself a [`View`], so embedding it as an
17/// element goes through the same [`ViewElement`] machinery as any other view.
18#[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 /// Embed this view as a cached [`ViewElement`] laid out at `style`.
35 ///
36 /// The rendered subtree is recycled from the previous frame unless
37 /// [Context::notify] was called on the backing entity since it was rendered
38 /// (or [Window::refresh] is called, which ignores caching).
39 pub fn cached(self, style: StyleRefinement) -> ViewElement<AnyView> {
40 ViewElement::new(self).cached(style)
41 }
42
43 /// Convert this to a weak handle.
44 pub fn downgrade(&self) -> AnyWeakView {
45 AnyWeakView {
46 entity: self.entity.downgrade(),
47 render: self.render,
48 }
49 }
50
51 /// Convert this to a [Entity] of a specific type.
52 /// If this handle does not contain a view of the specified type, returns itself in an `Err` variant.
53 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 /// Gets the [TypeId] of the underlying view.
64 pub fn entity_type(&self) -> TypeId {
65 self.entity.entity_type
66 }
67
68 /// The [`EntityId`] of this view.
69 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
82/// `AnyView` is the type-erased [`View`]: its `render` is a function pointer rather
83/// than a concrete type, but it participates in the reactive graph exactly like any
84/// other view via [`ViewElement`].
85impl 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
111/// A weak, dynamically-typed view handle.
112pub struct AnyWeakView {
113 entity: AnyWeakEntity,
114 render: fn(&AnyView, &mut Window, &mut App) -> AnyElement,
115}
116
117impl AnyWeakView {
118 /// Upgrade to a strong `AnyView` handle, if the view is still alive.
119 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 // Record the view's Render type name so the accessibility debug dump can
161 // attribute nodes to the view that produced them.
162 #[cfg(debug_assertions)]
163 window
164 .a11y
165 .view_type_names
166 .insert(view.entity_id(), std::any::type_name::<V>());
167 view.update(cx, |view, cx| view.render(window, cx).into_any_element())
168 }
169}
170
171/// A renderable that participates in GPUI's reactive graph — the unifying model
172/// behind [`Render`] and [`RenderOnce`].
173///
174/// When `entity_id()` returns `Some`, that id becomes the view's identity: it gets
175/// a unique element-id space (so internal `use_state` / `.id(..)` never collide
176/// across siblings) and `cx.notify()` on that entity re-renders only this view's
177/// subtree. `None` behaves like a stateless component.
178///
179/// You rarely implement `View` directly. `Entity<T: Render>` and any `T: RenderOnce`
180/// get a blanket impl below; implement it by hand only when a component needs both
181/// parent-supplied props *and* a backing entity for identity.
182pub trait View: 'static + Sized {
183 /// This view's identity, if it has one. A view typically holds the backing
184 /// entity as a field and returns its [`EntityId`] here.
185 ///
186 /// The id becomes this view's [`ElementId`], so two views keyed on the same
187 /// entity must not be rendered at the same position in the element tree
188 /// (e.g. as siblings under the same parent): their internal element state
189 /// (`use_state`, scroll offsets, etc.) would silently collide. Nesting is
190 /// fine — the id is scoped by the parent path.
191 fn entity_id(&self) -> Option<EntityId>;
192
193 /// Render this view into an element tree, consuming `self`.
194 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement;
195}
196
197/// A stateless component (`RenderOnce`) is a `View` with no identity.
198impl<T: RenderOnce> View for T {
199 fn entity_id(&self) -> Option<EntityId> {
200 None
201 }
202
203 #[inline]
204 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
205 RenderOnce::render(self, window, cx)
206 }
207}
208
209/// An entity that renders itself (`Render`) is a `View` keyed on its own id.
210impl<T: Render> View for Entity<T> {
211 fn entity_id(&self) -> Option<EntityId> {
212 Some(Entity::entity_id(self))
213 }
214
215 #[inline]
216 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
217 self.update(cx, |this, cx| {
218 Render::render(this, window, cx).into_any_element()
219 })
220 }
221}
222
223impl<T: Render> Entity<T> {
224 /// Embed this entity as a cached [`ViewElement`] laid out at `style`.
225 ///
226 /// The rendered subtree is reused until the entity is notified (or the
227 /// cached bounds / text style change). Caching requires a definite size:
228 /// a cached view is laid out from `style` and is *not* measured from its
229 /// contents. Use [`ViewElement::new`] (or `.child(entity)`) for the
230 /// uncached case.
231 #[track_caller]
232 pub fn cached(self, style: StyleRefinement) -> ViewElement<Entity<T>> {
233 ViewElement::new(self).cached(style)
234 }
235}
236
237/// The element type for [`View`] implementations. Wraps a `View` and hooks it
238/// into layout, prepaint, and paint. Constructed via [`ViewElement::new`].
239#[doc(hidden)]
240pub struct ViewElement<V: View> {
241 view: Option<V>,
242 entity_id: Option<EntityId>,
243 cached_style: Option<StyleRefinement>,
244 #[cfg(debug_assertions)]
245 source: &'static core::panic::Location<'static>,
246}
247
248impl<V: View> ViewElement<V> {
249 /// Wrap a [`View`] as an element.
250 #[track_caller]
251 pub fn new(view: V) -> Self {
252 let entity_id = view.entity_id();
253 ViewElement {
254 entity_id,
255 cached_style: None,
256 view: Some(view),
257 #[cfg(debug_assertions)]
258 source: core::panic::Location::caller(),
259 }
260 }
261
262 /// Enable caching of this view's rendered subtree, laid out at `style`.
263 /// The composer supplies the layout style because caching skips rendering
264 /// the contents to measure them.
265 ///
266 /// Crate-private on purpose: caching is only sound for entity-backed views,
267 /// where [`Context::notify`] is the contract that busts the cache. A stateless
268 /// view has no such contract, so a frozen subtree could never be invalidated.
269 /// Reach this through [`Entity::cached`] or [`AnyView::cached`], which are
270 /// entity-backed by construction.
271 pub(crate) fn cached(mut self, style: StyleRefinement) -> Self {
272 self.cached_style = Some(style);
273 self
274 }
275}
276
277impl<V: View> IntoElement for ViewElement<V> {
278 type Element = Self;
279
280 fn into_element(self) -> Self::Element {
281 self
282 }
283}
284
285struct ViewElementState {
286 prepaint_range: Range<PrepaintStateIndex>,
287 paint_range: Range<PaintIndex>,
288 cache_key: ViewElementCacheKey,
289 accessed_entities: FxHashSet<EntityId>,
290}
291
292struct ViewElementCacheKey {
293 bounds: Bounds<Pixels>,
294 content_mask: ContentMask<Pixels>,
295 text_style: TextStyle,
296}
297
298impl<V: View> Element for ViewElement<V> {
299 type RequestLayoutState = Option<AnyElement>;
300 type PrepaintState = Option<AnyElement>;
301
302 fn id(&self) -> Option<ElementId> {
303 self.entity_id.map(ElementId::View)
304 }
305
306 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
307 #[cfg(debug_assertions)]
308 return Some(self.source);
309
310 #[cfg(not(debug_assertions))]
311 return None;
312 }
313
314 fn request_layout(
315 &mut self,
316 _id: Option<&GlobalElementId>,
317 _inspector_id: Option<&InspectorElementId>,
318 window: &mut Window,
319 cx: &mut App,
320 ) -> (LayoutId, Self::RequestLayoutState) {
321 if let Some(entity_id) = self.entity_id {
322 // Stateful path: create a reactive boundary.
323 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 // Stateless path: isolate subtree via type name (no entity identity).
346 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 _inspector_id: Option<&InspectorElementId>,
366 bounds: Bounds<Pixels>,
367 element: &mut Self::RequestLayoutState,
368 window: &mut Window,
369 cx: &mut App,
370 ) -> Option<AnyElement> {
371 if let Some(entity_id) = self.entity_id {
372 // Stateful path.
373 window.set_view_id(entity_id);
374 window.with_rendered_view(entity_id, |window| {
375 if let Some(mut element) = element.take() {
376 element.prepaint(window, cx);
377 return Some(element);
378 }
379
380 window.with_element_state::<ViewElementState, _>(
381 global_id.unwrap(),
382 |element_state, window| {
383 let content_mask = window.content_mask();
384 let text_style = window.text_style();
385
386 if let Some(mut element_state) = element_state
387 && element_state.cache_key.bounds == bounds
388 && element_state.cache_key.content_mask == content_mask
389 && element_state.cache_key.text_style == text_style
390 && !window.dirty_views.contains(&entity_id)
391 && !window.refreshing
392 {
393 let prepaint_start = window.prepaint_index();
394 window.reuse_prepaint(element_state.prepaint_range.clone());
395 cx.entities
396 .extend_accessed(&element_state.accessed_entities);
397 let prepaint_end = window.prepaint_index();
398 element_state.prepaint_range = prepaint_start..prepaint_end;
399
400 return (None, element_state);
401 }
402
403 let refreshing = mem::replace(&mut window.refreshing, true);
404 let prepaint_start = window.prepaint_index();
405 let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
406 let mut element = self
407 .view
408 .take()
409 .unwrap()
410 .render(window, cx)
411 .into_any_element();
412 element.layout_as_root(bounds.size.into(), window, cx);
413 element.prepaint_at(bounds.origin, window, cx);
414 element
415 });
416
417 let prepaint_end = window.prepaint_index();
418 window.refreshing = refreshing;
419
420 (
421 Some(element),
422 ViewElementState {
423 accessed_entities,
424 prepaint_range: prepaint_start..prepaint_end,
425 paint_range: PaintIndex::default()..PaintIndex::default(),
426 cache_key: ViewElementCacheKey {
427 bounds,
428 content_mask,
429 text_style,
430 },
431 },
432 )
433 },
434 )
435 })
436 } else {
437 // Stateless path: just prepaint the element.
438 window.with_id(
439 ElementId::Name(std::any::type_name::<V>().into()),
440 |window| {
441 element.as_mut().unwrap().prepaint(window, cx);
442 },
443 );
444 Some(element.take().unwrap())
445 }
446 }
447
448 fn paint(
449 &mut self,
450 global_id: Option<&GlobalElementId>,
451 _inspector_id: Option<&InspectorElementId>,
452 _bounds: Bounds<Pixels>,
453 _request_layout: &mut Self::RequestLayoutState,
454 element: &mut Self::PrepaintState,
455 window: &mut Window,
456 cx: &mut App,
457 ) {
458 if let Some(entity_id) = self.entity_id {
459 // Stateful path.
460 window.with_rendered_view(entity_id, |window| {
461 let caching_disabled = window.is_inspector_picking(cx);
462 if self.cached_style.is_some() && !caching_disabled {
463 window.with_element_state::<ViewElementState, _>(
464 global_id.unwrap(),
465 |element_state, window| {
466 let mut element_state = element_state.unwrap();
467
468 let paint_start = window.paint_index();
469
470 if let Some(element) = element {
471 let refreshing = mem::replace(&mut window.refreshing, true);
472 element.paint(window, cx);
473 window.refreshing = refreshing;
474 } else {
475 window.reuse_paint(element_state.paint_range.clone());
476 }
477
478 let paint_end = window.paint_index();
479 element_state.paint_range = paint_start..paint_end;
480
481 ((), element_state)
482 },
483 )
484 } else {
485 element.as_mut().unwrap().paint(window, cx);
486 }
487 });
488 } else {
489 // Stateless path: just paint the element.
490 window.with_id(
491 ElementId::Name(std::any::type_name::<V>().into()),
492 |window| {
493 element.as_mut().unwrap().paint(window, cx);
494 },
495 );
496 }
497 }
498}
499
500/// A view that renders nothing
501pub struct EmptyView;
502
503impl Render for EmptyView {
504 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
505 Empty
506 }
507}
508