1#![cfg_attr(target_family = "wasm", no_main)]
2
3//! **view_example** — an end-to-end GPUI example demonstrating how Entity,
4//! Element, View, and Render compose together to build rich text components.
5//!
6//! ## Architecture
7//!
8//! Each module has a focused job:
9//!
10//! | Module | Trait | Job |
11//! |-----------------|---------------|--------------------------------------------------------|
12//! | `editor` | EntityView | Owns text, cursor, blink; renders via ExampleEditorText |
13//! | `input` | View | Single-line input with own state + cached editor child |
14//! | `editor_info` | View | Read-only stats display; zero-wiring, same editor entity |
15//! | `text_area` | ComponentView | Stateless multi-line wrapper; inner editor caches |
16//! | `main` (here) | Render | Root view; creates entities with `use_state`, assembles |
17//!
18//! ## Running
19//!
20//! ```sh
21//! cargo run --example view_example -p gpui
22//! ```
23//!
24//! ## Testing
25//!
26//! ```sh
27//! cargo test --example view_example -p gpui
28//! ```
29
30mod example_editor;
31mod example_editor_info;
32mod example_input;
33mod example_text_area;
34
35#[cfg(test)]
36mod example_tests;
37
38use gpui::{
39 App, Bounds, Context, Hsla, KeyBinding, Window, WindowBounds, WindowOptions, actions, div,
40 hsla, prelude::*, px, rgb, size,
41};
42use gpui_platform::application;
43
44use example_editor::ExampleEditor;
45use example_editor_info::EditorInfo;
46use example_input::{ExampleInput, ExampleInputState};
47use example_text_area::ExampleTextArea;
48
49actions!(
50 view_example,
51 [Backspace, Delete, Left, Right, Home, End, Enter, Quit,]
52);
53
54// ---------------------------------------------------------------------------
55// ViewExample — the root view using `Render` and `window.use_state()`
56// ---------------------------------------------------------------------------
57
58struct ViewExample {
59 input_color: Hsla,
60 textarea_color: Hsla,
61}
62
63impl ViewExample {
64 fn new() -> Self {
65 Self {
66 input_color: hsla(0., 0., 0.1, 1.),
67 textarea_color: hsla(250. / 360., 0.7, 0.4, 1.),
68 }
69 }
70}
71
72impl Render for ViewExample {
73 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
74 let input_state = window.use_state(cx, |window, cx| ExampleInputState::new(window, cx));
75 let input_editor = input_state.read(cx).editor.clone();
76 let textarea_editor = window.use_state(cx, |_window, cx| ExampleEditor::new(cx));
77 let input_color = self.input_color;
78 let textarea_color = self.textarea_color;
79
80 div()
81 .flex()
82 .flex_col()
83 .size_full()
84 .bg(rgb(0xf0f0f0))
85 .p(px(24.))
86 .gap(px(20.))
87 .child(
88 div()
89 .flex()
90 .flex_col()
91 .gap(px(4.))
92 .child(
93 div()
94 .text_sm()
95 .text_color(hsla(0., 0., 0.3, 1.))
96 .child("Single-line input (View with own state + cached editor)"),
97 )
98 .child(
99 ExampleInput::new(input_state)
100 .width(px(320.))
101 .color(input_color),
102 )
103 .child(EditorInfo::new(input_editor)),
104 )
105 .child(
106 div()
107 .flex()
108 .flex_col()
109 .gap(px(4.))
110 .child(div().text_sm().text_color(hsla(0., 0., 0.3, 1.)).child(
111 "Multi-line text area (TextArea — same entity type, different View)",
112 ))
113 .child(ExampleTextArea::new(textarea_editor, 5).color(textarea_color)),
114 )
115 .child(
116 div()
117 .flex()
118 .flex_col()
119 .gap(px(2.))
120 .mt(px(12.))
121 .text_xs()
122 .text_color(hsla(0., 0., 0.5, 1.))
123 .child("• ExampleInput: View with own state — caches independently")
124 .child("• EditorInfo: View on same editor — zero-wiring, auto-cached")
125 .child("• ExampleTextArea: ComponentView — stateless wrapper")
126 .child("• Press Enter in input to flash border (EditorInfo stays cached)")
127 .child("• Type to see both input and info update reactively"),
128 )
129 }
130}
131
132// ---------------------------------------------------------------------------
133// Entry point
134// ---------------------------------------------------------------------------
135
136fn run_example() {
137 application().run(|cx: &mut App| {
138 let bounds = Bounds::centered(None, size(px(500.0), px(500.0)), cx);
139 cx.bind_keys([
140 KeyBinding::new("backspace", Backspace, None),
141 KeyBinding::new("delete", Delete, None),
142 KeyBinding::new("left", Left, None),
143 KeyBinding::new("right", Right, None),
144 KeyBinding::new("home", Home, None),
145 KeyBinding::new("end", End, None),
146 KeyBinding::new("enter", Enter, None),
147 KeyBinding::new("cmd-q", Quit, None),
148 ]);
149
150 cx.open_window(
151 WindowOptions {
152 window_bounds: Some(WindowBounds::Windowed(bounds)),
153 ..Default::default()
154 },
155 |_, cx| cx.new(|_| ViewExample::new()),
156 )
157 .unwrap();
158
159 cx.on_action(|_: &Quit, cx| cx.quit());
160 cx.activate(true);
161 });
162}
163
164#[cfg(not(target_family = "wasm"))]
165fn main() {
166 run_example();
167}
168
169#[cfg(target_family = "wasm")]
170#[wasm_bindgen::prelude::wasm_bindgen(start)]
171pub fn start() {
172 gpui_platform::web_init();
173 run_example();
174}