diff --git a/crates/gpui/examples/view_example/example_editor_info.rs b/crates/gpui/examples/view_example/example_editor_info.rs new file mode 100644 index 0000000000000000000000000000000000000000..3112360f3fd46b76b0166da4dd53ad9a961ad450 --- /dev/null +++ b/crates/gpui/examples/view_example/example_editor_info.rs @@ -0,0 +1,46 @@ +//! `EditorInfo` — a read-only View over an `ExampleEditor` entity. +//! +//! Demonstrates zero-wiring reactivity: just return the entity from `entity()`, +//! read from it in `render()`, and caching + invalidation happen automatically. +//! No observers, no subscriptions, no manual `cx.notify()`. + +use gpui::{App, Entity, IntoViewElement, Window, div, hsla, prelude::*, px}; + +use crate::example_editor::ExampleEditor; + +#[derive(Hash, IntoViewElement)] +pub struct EditorInfo { + editor: Entity, +} + +impl EditorInfo { + pub fn new(editor: Entity) -> Self { + Self { editor } + } +} + +impl gpui::View for EditorInfo { + type Entity = ExampleEditor; + + fn entity(&self) -> Option> { + Some(self.editor.clone()) + } + + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + let editor = self.editor.read(cx); + let char_count = editor.content.len(); + let cursor = editor.cursor; + let is_focused = editor.focus_handle.is_focused(window); + + div() + .flex() + .gap(px(8.)) + .text_xs() + .text_color(hsla(0., 0., 0.45, 1.)) + .child(format!("{char_count} chars")) + .child("·") + .child(format!("cursor {cursor}")) + .child("·") + .child(if is_focused { "focused" } else { "unfocused" }) + } +} diff --git a/crates/gpui/examples/view_example/example_input.rs b/crates/gpui/examples/view_example/example_input.rs index 551a4d33af5ee52be5d8c9fc282076242d8747df..a17033ced38b3208b93ecfe1af516090e576cf7b 100644 --- a/crates/gpui/examples/view_example/example_input.rs +++ b/crates/gpui/examples/view_example/example_input.rs @@ -17,7 +17,7 @@ use crate::example_editor::ExampleEditor; use crate::{Backspace, Delete, End, Enter, Home, Left, Right}; pub struct ExampleInputState { - editor: Entity, + pub editor: Entity, focus_handle: FocusHandle, is_focused: bool, flash_count: usize, diff --git a/crates/gpui/examples/view_example/view_example_main.rs b/crates/gpui/examples/view_example/view_example_main.rs index f20a6e58d22d926cb07d2dbfc3bf91d1fe3b4440..e0ec702646fa188da74bb02aa5139286fe02068e 100644 --- a/crates/gpui/examples/view_example/view_example_main.rs +++ b/crates/gpui/examples/view_example/view_example_main.rs @@ -7,13 +7,13 @@ //! //! Each module has a focused job: //! -//! | Module | Layer | Job | -//! |-----------------|---------|----------------------------------------------------------| -//! | `editor` | Entity | Owns text, cursor, blink task, `EntityInputHandler` | -//! | `editor_text` | Element | Shapes text, paints cursor, wires `handle_input` | -//! | `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 | +//! | Module | Trait | Job | +//! |-----------------|---------------|--------------------------------------------------------| +//! | `editor` | EntityView | Owns text, cursor, blink; renders via ExampleEditorText | +//! | `input` | View | Single-line input with own state + cached editor child | +//! | `editor_info` | View | Read-only stats display; zero-wiring, same editor entity | +//! | `text_area` | ComponentView | Stateless multi-line wrapper; inner editor caches | +//! | `main` (here) | Render | Root view; creates entities with `use_state`, assembles | //! //! ## Running //! @@ -28,6 +28,7 @@ //! ``` mod example_editor; +mod example_editor_info; mod example_input; mod example_text_area; @@ -41,6 +42,7 @@ use gpui::{ use gpui_platform::application; use example_editor::ExampleEditor; +use example_editor_info::EditorInfo; use example_input::{ExampleInput, ExampleInputState}; use example_text_area::ExampleTextArea; @@ -70,6 +72,7 @@ impl ViewExample { impl Render for ViewExample { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let input_state = window.use_state(cx, |window, cx| ExampleInputState::new(window, cx)); + let input_editor = input_state.read(cx).editor.clone(); let textarea_editor = window.use_state(cx, |_window, cx| ExampleEditor::new(cx)); let input_color = self.input_color; let textarea_color = self.textarea_color; @@ -87,15 +90,17 @@ impl Render for ViewExample { .flex_col() .gap(px(4.)) .child( - div().text_sm().text_color(hsla(0., 0., 0.3, 1.)).child( - "Single-line input (Input — View with own state + cached editor)", - ), + div() + .text_sm() + .text_color(hsla(0., 0., 0.3, 1.)) + .child("Single-line input (View with own state + cached editor)"), ) .child( ExampleInput::new(input_state) .width(px(320.)) .color(input_color), - ), + ) + .child(EditorInfo::new(input_editor)), ) .child( div() @@ -115,13 +120,11 @@ impl Render for ViewExample { .mt(px(12.)) .text_xs() .text_color(hsla(0., 0., 0.5, 1.)) - .child("• ExampleEditor entity owns text, cursor, blink (EntityView)") - .child("• ExampleInput is a View with its own state — caches independently") - .child( - "• ExampleTextArea is a ComponentView — stateless wrapper, editor caches", - ) - .child("• Press Enter in input to flash border (only chrome re-renders)") - .child("• Entities created via window.use_state()"), + .child("• ExampleInput: View with own state — caches independently") + .child("• EditorInfo: View on same editor — zero-wiring, auto-cached") + .child("• ExampleTextArea: ComponentView — stateless wrapper") + .child("• Press Enter in input to flash border (EditorInfo stays cached)") + .child("• Type to see both input and info update reactively"), ) } }