Detailed changes
@@ -240,5 +240,5 @@ name = "mouse_pressure"
path = "examples/mouse_pressure.rs"
[[example]]
-name = "text_views"
-path = "examples/text_views/main.rs"
+name = "view_example"
+path = "examples/view_example/view_example_main.rs"
@@ -1,9 +1,9 @@
-//! The `Editor` entity — owns the truth about text content, cursor position,
+//! The `ExampleEditor` entity — owns the truth about text content, cursor position,
//! blink state, and keyboard handling.
//!
-//! Also contains `EditorText`, the low-level custom `Element` that shapes text
-//! and paints the cursor, and `EditorView`, a cached `View` wrapper that
-//! automatically pairs an `Editor` entity with its `EditorText` element.
+//! Also contains `ExampleEditorText`, the low-level custom `Element` that shapes text
+//! and paints the cursor, and `ExampleEditorView`, a cached `View` wrapper that
+//! automatically pairs an `ExampleEditor` entity with its `ExampleEditorText` element.
use std::hash::Hash;
use std::ops::Range;
@@ -18,7 +18,7 @@ use unicode_segmentation::*;
use crate::{Backspace, Delete, End, Home, Left, Right};
-pub struct Editor {
+pub struct ExampleEditor {
pub focus_handle: FocusHandle,
pub content: String,
pub cursor: usize,
@@ -26,7 +26,7 @@ pub struct Editor {
_blink_task: Task<()>,
}
-impl Editor {
+impl ExampleEditor {
pub fn new(cx: &mut Context<Self>) -> Self {
let blink_task = Self::spawn_blink_task(cx);
@@ -165,13 +165,13 @@ impl Editor {
}
}
-impl Focusable for Editor {
+impl Focusable for ExampleEditor {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
-impl EntityInputHandler for Editor {
+impl EntityInputHandler for ExampleEditor {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
@@ -258,26 +258,26 @@ impl EntityInputHandler for Editor {
}
// ---------------------------------------------------------------------------
-// EditorText — custom Element that shapes text & paints the cursor
+// ExampleEditorText — custom Element that shapes text & paints the cursor
// ---------------------------------------------------------------------------
-struct EditorText {
- editor: Entity<Editor>,
+struct ExampleEditorText {
+ editor: Entity<ExampleEditor>,
text_color: Hsla,
}
-struct EditorTextPrepaintState {
+struct ExampleEditorTextPrepaintState {
lines: Vec<ShapedLine>,
cursor: Option<PaintQuad>,
}
-impl EditorText {
- pub fn new(editor: Entity<Editor>, text_color: Hsla) -> Self {
+impl ExampleEditorText {
+ pub fn new(editor: Entity<ExampleEditor>, text_color: Hsla) -> Self {
Self { editor, text_color }
}
}
-impl IntoElement for EditorText {
+impl IntoElement for ExampleEditorText {
type Element = Self;
fn into_element(self) -> Self::Element {
@@ -285,9 +285,9 @@ impl IntoElement for EditorText {
}
}
-impl Element for EditorText {
+impl Element for ExampleEditorText {
type RequestLayoutState = ();
- type PrepaintState = EditorTextPrepaintState;
+ type PrepaintState = ExampleEditorTextPrepaintState;
fn id(&self) -> Option<gpui::ElementId> {
None
@@ -395,7 +395,7 @@ impl Element for EditorText {
None
};
- EditorTextPrepaintState {
+ ExampleEditorTextPrepaintState {
lines: shaped_lines,
cursor,
}
@@ -448,20 +448,20 @@ fn cursor_line_and_offset(content: &str, cursor: usize) -> (usize, usize) {
}
// ---------------------------------------------------------------------------
-// EditorView — a cached View that pairs an Editor entity with EditorText
+// ExampleEditorView — a cached View that pairs an ExampleEditor entity with ExampleEditorText
// ---------------------------------------------------------------------------
-/// A simple cached view that renders an `Editor` entity via the `EditorText`
+/// A simple cached view that renders an `ExampleEditor` entity via the `ExampleEditorText`
/// custom element. Use this when you want a bare editor display with automatic
/// caching and no extra chrome.
#[derive(IntoViewElement, Hash)]
-pub struct EditorView {
- editor: Entity<Editor>,
+pub struct ExampleEditorView {
+ editor: Entity<ExampleEditor>,
text_color: Hsla,
}
-impl EditorView {
- pub fn new(editor: Entity<Editor>) -> Self {
+impl ExampleEditorView {
+ pub fn new(editor: Entity<ExampleEditor>) -> Self {
Self {
editor,
text_color: hsla(0., 0., 0.1, 1.),
@@ -474,14 +474,14 @@ impl EditorView {
}
}
-impl gpui::View for EditorView {
- type State = Editor;
+impl gpui::View for ExampleEditorView {
+ type Entity = ExampleEditor;
- fn entity(&self) -> &Entity<Editor> {
- &self.editor
+ fn entity(&self) -> Option<Entity<ExampleEditor>> {
+ Some(self.editor.clone())
}
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
- EditorText::new(self.editor, self.text_color)
+ ExampleEditorText::new(self.editor, self.text_color)
}
}
@@ -1,6 +1,6 @@
-//! The `Input` view — a single-line text input component.
+//! The `ExampleInput` view — a single-line text input component.
//!
-//! Composes `EditorText` inside a styled container with focus ring, border,
+//! Composes `ExampleEditorText` inside a styled container with focus ring, border,
//! and action handlers. Implements the `View` trait with `#[derive(Hash)]`
//! so that prop changes (color, width) automatically invalidate the render
//! cache via `ViewElement::cached()`.
@@ -13,8 +13,8 @@ use gpui::{
prelude::*, px, white,
};
-use crate::editor::Editor;
-use crate::editor::EditorView;
+use crate::example_editor::ExampleEditor;
+use crate::example_editor::ExampleEditorView;
use crate::{Backspace, Delete, End, Enter, Home, Left, Right};
struct FlashState {
@@ -22,14 +22,14 @@ struct FlashState {
}
#[derive(Hash, IntoViewElement)]
-pub struct Input {
- editor: Entity<Editor>,
+pub struct ExampleInput {
+ editor: Entity<ExampleEditor>,
width: Option<Pixels>,
color: Option<Hsla>,
}
-impl Input {
- pub fn new(editor: Entity<Editor>) -> Self {
+impl ExampleInput {
+ pub fn new(editor: Entity<ExampleEditor>) -> Self {
Self {
editor,
width: None,
@@ -48,11 +48,11 @@ impl Input {
}
}
-impl gpui::View for Input {
- type State = Editor;
+impl gpui::View for ExampleInput {
+ type Entity = ExampleEditor;
- fn entity(&self) -> &Entity<Editor> {
- &self.editor
+ fn entity(&self) -> Option<Entity<ExampleEditor>> {
+ Some(self.editor.clone())
}
fn style(&self) -> Option<StyleRefinement> {
@@ -72,7 +72,7 @@ impl gpui::View for Input {
let is_focused = focus_handle.is_focused(window);
let text_color = self.color.unwrap_or(hsla(0., 0., 0.1, 1.));
let box_width = self.width.unwrap_or(px(300.));
- let editor = self.editor.clone();
+ let editor = self.editor;
let focused_border = hsla(220. / 360., 0.8, 0.5, 1.);
let unfocused_border = hsla(0., 0., 0.75, 1.);
@@ -125,7 +125,7 @@ impl gpui::View for Input {
}
})
.on_action({
- let flash_state = flash_state.clone();
+ let flash_state = flash_state;
move |_: &Enter, _window, cx| {
flash_state.update(cx, |state, cx| {
state.count += 1;
@@ -154,7 +154,7 @@ impl gpui::View for Input {
.line_height(px(20.))
.text_size(px(14.))
.text_color(text_color)
- .child(EditorView::new(editor).text_color(text_color));
+ .child(ExampleEditorView::new(editor).text_color(text_color));
if count > 0 {
base.with_animation(
@@ -1,4 +1,4 @@
-//! Tests for the `Editor` entity.
+//! Tests for the `ExampleEditor` entity.
//!
//! These use GPUI's test infrastructure which requires the `test-support` feature:
//!
@@ -12,28 +12,28 @@ mod tests {
use gpui::{Context, Entity, KeyBinding, TestAppContext, Window, prelude::*};
- use crate::editor::Editor;
- use crate::input::Input;
- use crate::text_area::TextArea;
+ use crate::example_editor::ExampleEditor;
+ use crate::example_input::ExampleInput;
+ use crate::example_text_area::ExampleTextArea;
use crate::{Backspace, Delete, End, Enter, Home, Left, Right};
struct InputWrapper {
- editor: Entity<Editor>,
+ editor: Entity<ExampleEditor>,
}
impl Render for InputWrapper {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
- Input::new(self.editor.clone())
+ ExampleInput::new(self.editor.clone())
}
}
struct TextAreaWrapper {
- editor: Entity<Editor>,
+ editor: Entity<ExampleEditor>,
}
impl Render for TextAreaWrapper {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
- TextArea::new(self.editor.clone(), 5)
+ ExampleTextArea::new(self.editor.clone(), 5)
}
}
@@ -51,11 +51,13 @@ mod tests {
});
}
- fn init_input(cx: &mut TestAppContext) -> (Entity<Editor>, &mut gpui::VisualTestContext) {
+ fn init_input(
+ cx: &mut TestAppContext,
+ ) -> (Entity<ExampleEditor>, &mut gpui::VisualTestContext) {
bind_keys(cx);
let (wrapper, cx) = cx.add_window_view(|_window, cx| {
- let editor = cx.new(|cx| Editor::new(cx));
+ let editor = cx.new(|cx| ExampleEditor::new(cx));
InputWrapper { editor }
});
@@ -69,11 +71,13 @@ mod tests {
(editor, cx)
}
- fn init_textarea(cx: &mut TestAppContext) -> (Entity<Editor>, &mut gpui::VisualTestContext) {
+ fn init_textarea(
+ cx: &mut TestAppContext,
+ ) -> (Entity<ExampleEditor>, &mut gpui::VisualTestContext) {
bind_keys(cx);
let (wrapper, cx) = cx.add_window_view(|_window, cx| {
- let editor = cx.new(|cx| Editor::new(cx));
+ let editor = cx.new(|cx| ExampleEditor::new(cx));
TextAreaWrapper { editor }
});
@@ -1,6 +1,6 @@
-//! The `TextArea` view — a multi-line text area component.
+//! The `ExampleTextArea` view — a multi-line text area component.
//!
-//! Same `Editor` entity, different presentation: taller box with configurable
+//! Same `ExampleEditor` entity, different presentation: taller box with configurable
//! row count. Demonstrates that the same entity type can back different `View`
//! components with different props and layouts.
@@ -9,19 +9,19 @@ use gpui::{
point, prelude::*, px, white,
};
-use crate::editor::Editor;
-use crate::editor::EditorView;
+use crate::example_editor::ExampleEditor;
+use crate::example_editor::ExampleEditorView;
use crate::{Backspace, Delete, End, Enter, Home, Left, Right};
#[derive(Hash, IntoViewElement)]
-pub struct TextArea {
- editor: Entity<Editor>,
+pub struct ExampleTextArea {
+ editor: Entity<ExampleEditor>,
rows: usize,
color: Option<Hsla>,
}
-impl TextArea {
- pub fn new(editor: Entity<Editor>, rows: usize) -> Self {
+impl ExampleTextArea {
+ pub fn new(editor: Entity<ExampleEditor>, rows: usize) -> Self {
Self {
editor,
rows,
@@ -35,11 +35,11 @@ impl TextArea {
}
}
-impl gpui::View for TextArea {
- type State = Editor;
+impl gpui::View for ExampleTextArea {
+ type Entity = ExampleEditor;
- fn entity(&self) -> &Entity<Editor> {
- &self.editor
+ fn entity(&self) -> Option<Entity<ExampleEditor>> {
+ Some(self.editor.clone())
}
fn style(&self) -> Option<StyleRefinement> {
@@ -57,7 +57,7 @@ impl gpui::View for TextArea {
let text_color = self.color.unwrap_or(hsla(0., 0., 0.1, 1.));
let row_height = px(20.);
let box_height = row_height * self.rows as f32 + px(16.);
- let editor = self.editor.clone();
+ let editor = self.editor;
div()
.id("text-area")
@@ -129,6 +129,6 @@ impl gpui::View for TextArea {
.line_height(row_height)
.text_size(px(14.))
.text_color(text_color)
- .child(EditorView::new(editor).text_color(text_color))
+ .child(ExampleEditorView::new(editor).text_color(text_color))
}
}
@@ -0,0 +1,13 @@
+# View Example — Plan
+
+## Done
+
+- Introduced `View` trait, `ComponentView` trait, and `ViewElement` struct as a unification of `Component`, `RenderOnce`, `AnyView`, and `Render`
+- Initialized example of the composition this can achieve with the editor
+
+## Next
+
+- Add a render log showing coarse-grained caching (rather than existing spot-caching)
+- Add tab index support so that the demo doesn't need mouse movement
+- Move focus handles out to the input and textarea, and stop blinking when not focused
+- De-fluff LLM generated code (remove excessive comments, simplify implementations, etc.)
@@ -1,6 +1,6 @@
#![cfg_attr(target_family = "wasm", no_main)]
-//! **text_views** — an end-to-end GPUI example demonstrating how Entity,
+//! **view_example** — an end-to-end GPUI example demonstrating how Entity,
//! Element, View, and Render compose together to build rich text components.
//!
//! ## Architecture
@@ -11,28 +11,28 @@
//! |-----------------|---------|----------------------------------------------------------|
//! | `editor` | Entity | Owns text, cursor, blink task, `EntityInputHandler` |
//! | `editor_text` | Element | Shapes text, paints cursor, wires `handle_input` |
-//! | `input` | View | Single-line input — composes `EditorText` with styling |
+//! | `input` | View | Single-line input — composes `ExampleEditorText` with styling |
//! | `text_area` | View | Multi-line text area — same entity, different layout |
//! | `main` (here) | Render | Root view — creates entities with `use_state`, assembles |
//!
//! ## Running
//!
//! ```sh
-//! cargo run --example text_views -p gpui
+//! cargo run --example view_example -p gpui
//! ```
//!
//! ## Testing
//!
//! ```sh
-//! cargo test --example text_views -p gpui
+//! cargo test --example view_example -p gpui
//! ```
-mod editor;
-mod input;
-mod text_area;
+mod example_editor;
+mod example_input;
+mod example_text_area;
#[cfg(test)]
-mod editor_test;
+mod example_tests;
use gpui::{
App, Bounds, Context, Hsla, KeyBinding, Window, WindowBounds, WindowOptions, actions, div,
@@ -40,25 +40,25 @@ use gpui::{
};
use gpui_platform::application;
-use editor::Editor;
-use input::Input;
-use text_area::TextArea;
+use example_editor::ExampleEditor;
+use example_input::ExampleInput;
+use example_text_area::ExampleTextArea;
actions!(
- text_views,
+ view_example,
[Backspace, Delete, Left, Right, Home, End, Enter, Quit,]
);
// ---------------------------------------------------------------------------
-// Example — the root view using `Render` and `window.use_state()`
+// ViewExample — the root view using `Render` and `window.use_state()`
// ---------------------------------------------------------------------------
-struct Example {
+struct ViewExample {
input_color: Hsla,
textarea_color: Hsla,
}
-impl Example {
+impl ViewExample {
fn new() -> Self {
Self {
input_color: hsla(0., 0., 0.1, 1.),
@@ -67,10 +67,10 @@ impl Example {
}
}
-impl Render for Example {
+impl Render for ViewExample {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- let input_editor = window.use_state(cx, |_window, cx| Editor::new(cx));
- let textarea_editor = window.use_state(cx, |_window, cx| Editor::new(cx));
+ let input_editor = window.use_state(cx, |_window, cx| ExampleEditor::new(cx));
+ let textarea_editor = window.use_state(cx, |_window, cx| ExampleEditor::new(cx));
let input_color = self.input_color;
let textarea_color = self.textarea_color;
@@ -90,9 +90,13 @@ impl Render for Example {
div()
.text_sm()
.text_color(hsla(0., 0., 0.3, 1.))
- .child("Single-line input (Input — View with cached EditorText)"),
+ .child("Single-line input (Input — View with cached ExampleEditorText)"),
)
- .child(Input::new(input_editor).width(px(320.)).color(input_color)),
+ .child(
+ ExampleInput::new(input_editor)
+ .width(px(320.))
+ .color(input_color),
+ ),
)
.child(
div()
@@ -102,7 +106,7 @@ impl Render for Example {
.child(div().text_sm().text_color(hsla(0., 0., 0.3, 1.)).child(
"Multi-line text area (TextArea — same entity type, different View)",
))
- .child(TextArea::new(textarea_editor, 5).color(textarea_color)),
+ .child(ExampleTextArea::new(textarea_editor, 5).color(textarea_color)),
)
.child(
div()
@@ -112,9 +116,9 @@ impl Render for Example {
.mt(px(12.))
.text_xs()
.text_color(hsla(0., 0., 0.5, 1.))
- .child("• Editor entity owns state, blink task, EntityInputHandler")
- .child("• EditorText element shapes text, paints cursor, wires handle_input")
- .child("• Input / TextArea views compose EditorText with container styling")
+ .child("• ExampleEditor entity owns state, blink task, EntityInputHandler")
+ .child("• ExampleEditorText element shapes text, paints cursor, wires handle_input")
+ .child("• Input / TextArea views compose ExampleEditorText with container styling")
.child("• ViewElement::cached() enables render caching via #[derive(Hash)]")
.child("• Entities created via window.use_state()"),
)
@@ -144,7 +148,7 @@ fn run_example() {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
- |_, cx| cx.new(|_| Example::new()),
+ |_, cx| cx.new(|_| ViewExample::new()),
)
.unwrap();
@@ -346,10 +346,10 @@ mod any_view {
/// }
///
/// impl View for Counter {
-/// type State = CounterState;
+/// type Entity = CounterState;
///
-/// fn entity(&self) -> &Entity<CounterState> {
-/// &self.state
+/// fn entity(&self) -> Option<Entity<CounterState>> {
+/// Some(self.state.clone())
/// }
///
///
@@ -364,10 +364,16 @@ mod any_view {
/// ```
pub trait View: 'static + Sized + Hash {
/// The entity type that backs this view's state.
- type State: 'static;
+ type Entity: 'static;
- /// Returns a reference to the entity that backs this view.
- fn entity(&self) -> &Entity<Self::State>;
+ /// Returns the entity that backs this view, if any.
+ ///
+ /// When `Some`, the view creates a reactive boundary in the element tree —
+ /// `cx.notify()` on the entity only re-renders this view's subtree.
+ ///
+ /// When `None`, the view behaves like a stateless component with subtree
+ /// isolation via its type name (similar to [`RenderOnce`](crate::RenderOnce)).
+ fn entity(&self) -> Option<Entity<Self::Entity>>;
/// Render this view into an element tree. Takes ownership of self,
/// consuming the component props. The entity state persists across frames.
@@ -385,6 +391,49 @@ pub trait View: 'static + Sized + Hash {
}
}
+/// A stateless component that renders an element tree without an entity.
+///
+/// This is the `View` equivalent of [`RenderOnce`](crate::RenderOnce). Types that
+/// implement `ComponentView` get a blanket implementation of [`View`] with
+/// `entity()` returning `None` and `style()` returning `None` — meaning no
+/// reactive boundary, no caching, just subtree isolation via the type name.
+///
+/// # Example
+///
+/// ```ignore
+/// #[derive(Hash, IntoViewElement)]
+/// struct Greeting {
+/// name: SharedString,
+/// }
+///
+/// impl ComponentView for Greeting {
+/// fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
+/// div().child(format!("Hello, {}!", self.name))
+/// }
+/// }
+/// ```
+pub trait ComponentView: 'static + Sized + Hash {
+ /// Render this component into an element tree. Takes ownership of self,
+ /// consuming the component props.
+ fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement;
+}
+
+impl<T: ComponentView> View for T {
+ type Entity = ();
+
+ fn entity(&self) -> Option<Entity<()>> {
+ None
+ }
+
+ fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
+ ComponentView::render(self, window, cx)
+ }
+
+ fn style(&self) -> Option<StyleRefinement> {
+ None
+ }
+}
+
/// An element that wraps a [`View`], creating a reactive boundary in the element tree.
///
/// This is the stateful counterpart to [`Component`](crate::Component) — where `Component<C>`
@@ -405,7 +454,7 @@ pub trait View: 'static + Sized + Hash {
#[doc(hidden)]
pub struct ViewElement<V: View> {
view: Option<V>,
- entity_id: EntityId,
+ entity_id: Option<EntityId>,
props_hash: u64,
cached_style: Option<StyleRefinement>,
#[cfg(debug_assertions)]
@@ -419,31 +468,20 @@ impl<V: View> ViewElement<V> {
#[track_caller]
pub fn new(view: V) -> Self {
use std::hash::Hasher;
- let entity_id = view.entity().entity_id();
+ let entity_id = view.entity().map(|e| e.entity_id());
+ let cached_style = view.style();
let mut hasher = std::collections::hash_map::DefaultHasher::new();
view.hash(&mut hasher);
let props_hash = hasher.finish();
ViewElement {
entity_id,
props_hash,
- cached_style: None,
+ cached_style,
view: Some(view),
#[cfg(debug_assertions)]
source: core::panic::Location::caller(),
}
}
-
- /// Enable caching for this view element. When cached, the view's render,
- /// prepaint, and paint will all be reused from the previous frame if the
- /// entity hasn't been notified and the props hash hasn't changed.
- ///
- /// The provided style defines the outer layout of this view in its parent
- /// (since the actual content render is deferred until we know if caching
- /// can be used).
- pub fn cached(mut self, style: StyleRefinement) -> Self {
- self.cached_style = Some(style);
- self
- }
}
impl<V: View> IntoElement for ViewElement<V> {
@@ -473,7 +511,7 @@ impl<V: View> Element for ViewElement<V> {
type PrepaintState = Option<AnyElement>;
fn id(&self) -> Option<ElementId> {
- Some(ElementId::View(self.entity_id))
+ self.entity_id.map(ElementId::View)
}
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
@@ -491,19 +529,34 @@ impl<V: View> Element for ViewElement<V> {
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
- window.with_rendered_view(self.entity_id, |window| {
- let caching_disabled = window.is_inspector_picking(cx);
- match self.cached_style.as_ref() {
- // Cached path: defer render, use style for layout.
- // Render will happen in prepaint only on cache miss.
- 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)
+ 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))
+ }
}
- // Non-cached path: render eagerly.
- _ => {
+ })
+ } else {
+ // Stateless path: isolate subtree via type name (like Component<C>).
+ window.with_id(
+ ElementId::Name(std::any::type_name::<V>().into()),
+ |window| {
let mut element = self
.view
.take()
@@ -512,9 +565,9 @@ impl<V: View> Element for ViewElement<V> {
.into_any_element();
let layout_id = element.request_layout(window, cx);
(layout_id, Some(element))
- }
- }
- })
+ },
+ )
+ }
}
fn prepaint(
@@ -526,76 +579,83 @@ impl<V: View> Element for ViewElement<V> {
window: &mut Window,
cx: &mut App,
) -> Option<AnyElement> {
- window.set_view_id(self.entity_id);
- window.with_rendered_view(self.entity_id, |window| {
- // Non-cached path: element was rendered in request_layout, just prepaint it.
- if let Some(mut element) = element.take() {
- element.prepaint(window, cx);
- return Some(element);
- }
+ 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);
+ }
- // Cached path: check cache, render only on miss.
- window.with_element_state::<ViewElementState, _>(
- global_id.unwrap(),
- |element_state, window| {
- let content_mask = window.content_mask();
- let text_style = window.text_style();
+ 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
+ && element_state.cache_key.props_hash == self.props_hash
+ && !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);
+ }
- // Cache hit: entity clean, props unchanged, bounds stable.
- // Skip render, prepaint, and paint entirely.
- 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
- && element_state.cache_key.props_hash == self.props_hash
- && !window.dirty_views.contains(&self.entity_id)
- && !window.refreshing
- {
+ let refreshing = mem::replace(&mut window.refreshing, true);
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;
+ 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
+ });
- return (None, element_state);
- }
-
- // Cache miss: render now, layout as root, prepaint.
- 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,
- props_hash: self.props_hash,
+ 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,
+ props_hash: self.props_hash,
+ },
},
- },
- )
+ )
+ },
+ )
+ })
+ } 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(
@@ -608,36 +668,45 @@ impl<V: View> Element for ViewElement<V> {
window: &mut Window,
cx: &mut App,
) {
- window.with_rendered_view(self.entity_id, |window| {
- let caching_disabled = window.is_inspector_picking(cx);
- if self.cached_style.is_some() && !caching_disabled {
- // Cached path: check if we rendered or reused.
- 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 {
- // Non-cached path: just paint the element.
- element.as_mut().unwrap().paint(window, cx);
- }
- });
+ 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);
+ },
+ );
+ }
}
}
@@ -14,12 +14,7 @@ pub fn derive_into_view_element(input: TokenStream) -> TokenStream {
type Element = gpui::ViewElement<Self>;
fn into_element(self) -> Self::Element {
- let style = gpui::View::style(&self);
- let element = gpui::ViewElement::new(self);
- match style {
- Some(s) => element.cached(s),
- None => element,
- }
+ gpui::ViewElement::new(self)
}
}
};